阶段总结-Java基础-超进阶
Gitee项目地址:https://gitee.com/zc10010/java_interview_guide/tree/master/知识点话术
项目叫话术,但是我觉得作为知识点学习是挺不错的。
里面的内容就是概括一下,方便记忆,了解细节可以去我的博客看
地址:https://www.cnblogs.com/zwtblog/
- SpringBoot
- SpringCloud
- 1、什么是Springcloud
- 2、服务注册和服务发现是什么意思,springcloud是如何实现的
- 3、负载均衡的意义是什么
- 4、hystrix介绍
- 5、Eureka和ZooKeeper都可以提供服务注册与发现的功能,请说说两个的区别
- ElasticSearch话术
- 1、介绍一下ElasticSearch,以及在项目中的应用
- 2、为什么使用ES?
- 3、 什么是桶(bucket)?什么是度量(metrics)?
- 4、ES内部存储的存储结构
- 5、描述一下Elasticsearch更新和删除文档的过程
- 6、详细描述一下Elasticsearch搜索的过程
- 7、在并发情况下,Elasticsearch如果保证读写一致
- 8、Elasticsearch是如何实现Master选举的
- 页面静态化
- RabbitMQ
- 授权认证
- Nginx
- Redis
- 前端
- Spring
- SpringMVC
- MyBatis
- 1、介绍一下MyBatis,说一下它的优点和缺点是什么?
- 2、MyBatis与Hibernate有哪些不同?
- 3、#{}和${}的区别是什么?
- 4、当实体类中的属性名和表中的字段名不一样 ,怎么办 ?
- 5、通常一个Xml映射文件,都会写一个Dao接口与之对应,这个Dao接口的工作原理是什么?Dao接口里的方法,参数不同时,方法能重载吗?
- 6、MyBatis 如何执行批量插入?
- 7、MyBatis 如何获取自动生成的(主)键值?
- 8、在mapper中如何传递多个参数?
- 9、MyBatis有哪些动态sql?
- 10、Xml映射文件中,除了常见的select|insert|updae|delete标签之外,还有哪些标签?
- 11、MyBatis的Xml映射文件中,不同的Xml映射文件,id是否可以重复?
- 12、MyBatis的一级、二级缓存?
- 13、使用MyBatis的mapper接口调用时有哪些要求?
- 14、MyBatis plus 了解过么?和MyBatis有啥区别?
- 15、MyBatis框架及原理?
- 锁
- 1、介绍一下乐观锁和悲观锁
- 2、介绍一下公平锁和非公平锁
- 3、重入锁(递归锁)和不可重入锁(自旋锁)
- 4、共享锁和独占锁
- 5、synchronized和ThreadLocal的区别
- 6、ConcurrentHashMap如何实现线程安全
- MySQL
- 1、解释一下单列索引和联合索引
- 2、使用索引查询的优缺点
- 3、mysql存储引擎都有哪些,有什么区别
- 4、创建索引的原则
- 5、如何查看查询语句索引是否生效
- 6、有没有做过数据库建模,自己是设计表和模块
- 7、左连接、右连接、内连接的区别
- 8、 count(1)和count(*) 有什么区别
- 9、mysql查询语句的优化?
- 10、mysql批量插入5000条数据如何优化?
- 11、mysql查询重复数据?
- 12、了解过MySQL存储过程和视图吗,介绍一下
- 13、where和having的区别
- 14、数据库三范式
- 15、select语句的执行顺序
- JVM
- Linux
SpringBoot
地址:https://www.cnblogs.com/zwtblog/tag/SpringBoot/
1、什么是springboot
SpringBoot是Spring项目中的一个子工程,其实人们把Spring Boot 称为搭建程序的脚手架
。
其最主要作用就是帮我们快速的构建庞大的spring项目,并且尽可能的减少一切xml配置,做到开箱即用,迅速上手,让我们关注与业务而非配置。
2、为什么要用springboot
Spring Boot 优点非常多,如:
一、独立运行 Spring Boot而且内嵌了各种servlet容器,Tomcat、Jetty等,
现在不再需要打成war包部署到容器中,Spring Boot只要打成一个可执行的 jar包就能独立运行,所有的依赖包都在一个jar包内。
二、简化配置 spring-boot-starter-web启动器自动依赖其他组件,简少了maven的配置。
三、自动配置 Spring Boot能根据当前类路径下的类、jar包来自动配置bean,如添加一个spring-boot-starter-web启动器就能拥有web的功能,无需其他配置。
四、无代码生成和XML配置 Spring Boot配置过程中无代码生成,也无需XML配置文件就能完成所有配置工作,这一切都是借助于条件注解完成的,这也是Spring4.x的核心功能之一。
五、应用监控 Spring Boot提供一系列端点可以监控服务及应用,做健康检测
3、springboot有哪些优点
- 减少开发,测试时间和努力。
- 使用 JavaConfig 有助于避免使用 XML。
- 避免大量的 Maven 导入和各种版本冲突。
- 通过提供默认值快速开始开发。
- 没有单独的 Web 服务器需要。这意味着你不再需要启动 Tomcat,Glassfish或其他任何东西。
- 需要更少的配置 因为没有 web.xml 文件。只需添加用@ Configuration 注释的类,然后添加用@Bean 注释的方法,Spring 将自动加载对象并像以前一样对其进行管理。您甚至可以将@Autowired 添加到 bean 方法中,以使 Spring 自动装入需要的依赖关系中。
- 基于环境的配置 使用这些属性,您可以将您正在使用的环境传递到应用程序:-Dspring.profilES.active = {enviornment}。在加载主应用程序属性文件后,Spring 将在(application{environment} .propertiES)中加载后续的应用程序属性文件。
4、Spring Boot 的核心注解是哪个?它主要由哪几个注解组成的?
启动类上面的注解是@SpringBootApplication,它也是 Spring Boot 的核心注解,主要组合包含了以下 3 个注解:
@SpringBootConfiguration:组合了 @Configuration 注解,实现配置文件的功能。
@EnableAutoConfiguration:打开自动配置的功能,也可以关闭某个自动配置的选项,如关闭数据源自动配置功能:@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })。
@ComponentScan:Spring组件扫描,从当前类所在的包以及子包扫描,之外的包扫描不到,所以我们在开发的时候,所有的类都在主类的子包下
5、springboot项目有哪几种运行方式
- 打包用命令或者放到容器中运行
- 用 Maven/Gradle 插件运行
- 直接执行 main 方法运行
6、如何理解springboot中的starters?
Starters可以理解为启动器,它包含了一系列可以集成到应用里面的依赖包,
你可以一站式集成Spring及其他技术,而不需要到处找示例代码和依赖包。
如你想使用Spring JPA访问数据库,只要加入springboot-starter-data-jpa启动器依赖就能使用了。
Starters包含了许多项目中需要用到的依赖,它们能快速持续的运行,都是一系列得到支持的管理传递性依赖。
7、springboot自动配置原理
这个就得从springboot项目的核心注解@SpringbootApplication说起了,这个注解包含了三个注解,
其中一个是@EnableAutoConfiguration注解,这个注解主要是开启自动配置的,这个注解会"猜"你将如何配置 spring,前提是你已经添加 了 jar 依赖项,
springboot默认有一个spring-boot-autoconfigure包,大多数常用的第三方的配置都自动集成了,像Redis、ES等,这里边有一个META-INF/spring.factoriES
文件,
这里边定义了所有需要加载的bean的全路径,spring会根据反射的原理,创建这些对象,放到IOC容器中,
加载时需要的参数,通过JavaConfig的方式加载配置文件中的参数然后创建了对应的对象,这就是自动配置的原理
SpringCloud
1、什么是Springcloud
Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器框架。Spring使你能够编写更干净、更可管理、并且更易于测试的代码。
Spring MVC是Spring的一个模块,一个web框架。通过Dispatcher Servlet, ModelAndView 和 View RESolver,开发web应用变得很容易。主要针对的是网站应用程序或者服务开发——URL路由、SESsion、模板引擎、静态Web资源等等。
Spring配置复杂,繁琐,所以推出了Spring boot,约定优于配置,简化了spring的配置流程。
Spring Cloud构建于Spring Boot之上,是一个关注全局的服务治理框架。
介绍链接:https://www.cnblogs.com/zwtblog/p/15153314.html#简单介绍
2、服务注册和服务发现是什么意思,springcloud是如何实现的
在微服务项目中,我们通常会用到服务之间的相互调用,
我们通常在属性文件中进行所有的需要请求的微服务的地址端口等信息。
随着越来越多的 服务开发和部署,添加和修改这些属性变得更加复杂。
有些服务可能会下架,而某些位置可 能会发生变化。手动更改属性可能会产生问题。
Eureka 提供了服务注册和服务发现的功能,服务注册是让所有微服务将自己的信息注册到注册中心,服务发现是让微服务可以拉取注册中心里的服务列表,方便结合feign进行远程调用,由于所有服务都在 Eureka 服务器上注册并通过调用 Eureka 服务器完成查找, 因此无需处理服务地点的任何更改和处理。
3、负载均衡的意义是什么
我觉得负载均衡的主要意义就是,避免单一应用由于并发等原因,导致应用宕机从而导致系统整体无法使用,多负载同时工作,可以很好的解决高并发的问题,实现服务的高可用。
在我们项目中,服务的接入层由nginx管理,用户请求经过nginx之后,根据相应的域名,转发到对应的服务器去,由于nginx只负责请求转发,没有业务逻辑处理,所以效率上非常高。
nginx支持的负载均衡策略有很多,我们在nginx.conf中配置upstream模块即可。
首先是轮询,默认的就是这种方式
第二种权重的方式,就是根据服务器的性能,配置较大的权重,nginx就会分配更多的请求
第三种是IP Hash的方式,nginx会根据请求的ip地址,根据hash运算,然后分配相应的服务器,后续来自同一ip的请求都会分配到这个服务器上
第四种是最少连接数,把请求转发给连接数较少的后端服务器
还有就是可以根据响应时间和url hash来做处理,我们项目中配置的是权重的方式。
在我们项目的微服务架构中,Feign和Zuul都集成了ribbon的的功能,它是负责在微服务之间相互调用的时候,帮我们实现负载均衡的功能,我们在将微服务注册到注册中心时,如果服务名称一致,就默认为一个集群,在进行远程调用或者路由转发的时候,都可以均衡的访问响应的服务器,ribbon的负载均衡机制有轮训和随机,默认是轮训,当然也可以自定义负载均衡的策略。
4、hystrix介绍
hystrix在微服务架构中充当熔断器的功能,它提供了资源隔离、限流、熔断、降级、运维监控等功能,
在微服务架构中通常会有多个微服务之间相互调用,基础服务的故障可能会导致级联故障,进而造成整个系统不可用的情况,这种现象被称为服务雪崩效应。
服务雪崩效应是一种因“服务提供者”的不可用导致“服务消费者”的不可用,并将不可用逐渐放大的过程。
熔断器的原理很简单,如同电力过载保护器。
它可以实现快速失败,如果它在一段时间内侦测到许多类似的错误,会强迫其以后的多个调用快速失败,不再访问远程服务器,直接执行回调方法,返回回调方法中的友好数据或者缓存数据,从而防止应用程序不断地尝试执行可能会失败的操作,使得应用程序继续执行而不用等待修正错误,或者浪费 CPU时间去等到长时间的超时产生。
熔断器也可以使应用程序能够诊断错误是否已经修正,如果已经、修正,应用程序会再次尝试调用操作。
当 Hystrix Command 请求后端服务失败数量超过一定比例(默认 50%),断路器会切换到开路状态(Open)。
这时所有请求会直接失败而不会发送到后端服务.。
断路器保持在开路状态一段时间后(默认 5 秒), 自动切换到半开路状态(HALF-OPEN)。
这时会判断下一次请求的返回情况,如果请求成功, 断路器切回闭路状态(CLOSED), 否则重新切换到开路状态(OPEN). Hystrix 的断路器就像我们家庭电路中的保险丝, 一旦后端服务不可用, 断路器会直接切断请求链, 避免发送大量无效请求影响系统吞吐量, 并且断路器有自我检测并恢复的能力。
5、Eureka和ZooKeeper都可以提供服务注册与发现的功能,请说说两个的区别
1、ZooKeeper保证的是CP,就是一致性和容错,Eureka保证的是AP,就是可用性和容错。
ZooKeeper在选举期间注册服务瘫痪,虽然服务最终会恢复,但是选举期间不可用的,只有等到选举结束之后,所有数据同步之后才能使用。
Eureka各个节点是平等关系,只要有一台Eureka就可以保证服务可用,而查询到的数据并不是最新的,自我保护机制会导致Eureka不再从注册列表移除因长时间没收到心跳而应该过期的服务。
Eureka仍然能够接受新服务的注册和查询请求,但是不会被同步到其他节点(高可用),当网络稳定时,当前实例新的注册信息会被同步到其他节点中(最终一致性)
Eureka可以很好的应对因网络故障导致部分节点失去联系的情况,而不会像ZooKeeper一样使得整个注册系统瘫痪
2、ZooKeeper有Leader和Follower角色,Eureka各个节点平等
3、ZooKeeper采用过半数存活原则,Eureka采用自我保护机制解决分区问题
4、Eureka本质上是一个微服务的一个模块,而ZooKeeper是一个进程,需要单独安装
ElasticSearch话术
1、介绍一下ElasticSearch,以及在项目中的应用
Java开发的基于lucene分布式全文搜索引擎。
基于rEStful Web接口。在检索领域相当优秀,在我们项目中主要是负责检索商品信息。
商品信息构成是比较复杂的,并且数据量巨大,至少会有几十万,如果使用MySQL做检索,效率会非常低,并且对MySQL造成很大的压力。
在使用过程中主要需要做几件事:
1、ES服务的安装,中文分词器使用IK分词器,这个主要是运维负责;
2、在项目中集成SpringDataElasticSearch框架,用来操作ES;
3、创建实体类,标注了一个Document注解,这个注解里声明了这个索引库的名称以及它的类型,还有他的分片儿信息,还有他的副本信息。
在这个类中创建所有跟商品相关的字段。同样会有一些注解去标识他的数据类型,
他的比如说id字段会有一个id注解,
其他字段用@Field注解标注在ES中存储的数据类型,是否进行分词等,有些字段也可以不加任何注解,
ES会根据存储的数据去判断字段在ES中存储的类型。
等其他的字段都建好之后,然后又新添加了一个统一的搜索字段,我们给它定义的名称叫all,我们会把经常搜索的数据全部定义到这个字段里。
比方说商品标题、副标题、品牌等;
4、初始全量数据导入
使用SpringBoot测试类实现导入,分批从MySQL中取出,组装数据到,保存到ES中
5、修改、新增、删除等增量数据导入
使用RabbitMQ实现,商品上下架的时候发送MQ消息,搜索微服务以及静态页面微服务实现ES数据的同步和静态页面的数据同步操作
6、使用ES实现搜索
- 使用all进行分词查询,搜索时,按照matchQuery做的,这个方法的做法是会把输入的关键词分词之后,然后去匹配,匹配的规则我们设置的是and的方式匹配,就是分词之后每个词条都匹配才算匹配,我们在项目里设置了个all字段,会把所有可能被分词的字段都会放到这个里边,只对这一个字段设置分词,关键字搜索的时候,都会去匹配这个字段,
- 使用规格参数聚合实现商品搜索规格参数的渲染
- 使用分类和品牌聚合实现商品分类和品牌查询的渲染
- 使用布尔查询实现规格参数、品牌、分类的过滤
- 实现分页、排序等需求
2、为什么使用ES?
因为在我们商城中的数据,将来会非常多,
所以采用以往的模糊查询,模糊查询前置配置,会放弃索引,导致商品查询是全表扫面,
在百万级别的数据库中,效率非常低下,而我们使用ES做一个全文索引,我们将经常查询的商品的某些字段,比如说商品名,描述、价格还有id这些字段我们放入我们索引库里,
ES内部有个倒排索引的机制,普通的索引的原理是通过id寻找数据,而倒排索引是通过数据寻找id,
它的大概原理是把需要分词的数据通过ik分词器分词之后,记录出每个词条对应的文档id,在进行搜索的时候,将搜索关键字分词之后,找到每个词条的文档id,然后在进行通过id搜索操作
大大提高了全文检索的效率,这也是在全文检索方面最常用的技术。
3、 什么是桶(bucket)?什么是度量(metrics)?
桶,是按照某种方式对数据进行分组,每一组数据在ES中称为一个桶
,例如我们根据国籍对人划分,可以得到中国桶
、英国桶
,日本桶
……
分桶的方式有很多,比如按日期阶梯分组、按数值阶梯分组、按词条内容分组、按数值和日期范围分组等。
度量,分组完成以后,我们一般会对组中的数据进行聚合运算,例如求平均值、最大、最小、求和等,这些在ES中称为度量
。度量有求平均值、求最大最小值、求百分比、求和等。
4、ES内部存储的存储结构
ES内部默认就是分布式的存储结构,每当创建一个索引库的时候,
我们需要指定当前索引库的分片数和副本数,分片数就是把数据分布式的存放在分片上,所有分片的数据加起来就是整个索引库的数据量,分片数越多,数据存放的越分散,搜索的时候,
ES会检索每个分片的伤的数据一起返回。
副本数是指在数据进入索引库的时候,同时需要备份的数量,如果副本数量越多,那么需要消耗的存储空间就会越多,过多的话会造成空间浪费,一般生产中使用的时候都用的默认的配置,就是5个分片,2个副本。
5、描述一下Elasticsearch更新和删除文档的过程
1、删除和更新也都是写操作,但是 Elasticsearch 中的文档是不可变的,因此不能被删除或者改动以展示其变更;
2、在文档被创建时,ES会为该文档指定一个版本号,当执行更新时,旧版本的文档在.del 文件中被标记为删除,新版本的文档被索引到一个新段。
旧版本的文档依然能匹配查询,但是会在结果中被过滤掉。删除同理
6、详细描述一下Elasticsearch搜索的过程
1、搜索被执行成一个两阶段过程,我们称之为 Query Then Fetch;
2、在初始查询阶段时,查询会广播到每个索引的主分片或者副本分片。搜索并构建一个优先队列。
3、每个分片返回各自优先队列中 所有文档的 ID 和排序值 给协调节点来合并结果列表。
4、协调节点将结果列表中的ID对应的文档取回,返回结果给客户端。
7、在并发情况下,Elasticsearch如果保证读写一致
整体使用版本号机制的乐观锁来控制;
对于写操作,默认为只有当大多数分片可用时才允许写操作。
但即使大多数可用,也可能存在因为网络等原因导致写入副本失败,分片将会在一个不同的节点上重建直到成功。
对于读操作,可以设置副本为同步状态(默认),就是当主副分片都完成后才会返回;
如果设置副本为异步时,也可以通过参数设置来只查询主分片,确查询最新数据。
8、Elasticsearch是如何实现Master选举的
1、ES的选主是 ZenDiscovery 模块负责,
首先配置每个节点是否可能成为master节点,当ZenDiscovery 通过ping发现多数节点认为当前没有master的时候,发起选举
2、选举时,把所有能做master的节点根据节点id字典排序,选出第一个节点暂定为主节点
3、然后再发起投票,票数过半并且该节点自己也选举自己,那这个节点就是 master。
页面静态化
首先说说为什么要进行页面静态化
从搜索这里可以看出,搜索列表展示的是固定30条数据,搜索的访问量已经很巨大了,那么详情页面的访问量是30倍的搜索量,如果放在整个百万级的用户量上来说,这个访问量是很巨大的,所以我们就不能每次访问详情页面的时候,都去服务端请求数据再展示
其次是这个详情页面来说,不会经常频繁的变动,所以每次去服务端取的数据基本都是一致的,所以我们就得想办法去解决如何快速的响应这么高的访问量
我们项目中服务端的请求和前端的请求,都是通过Nginx做的反向代理,
那么我们在项目中的方案是基于两个点去解决的,首先呢是结合模版引擎技术,用户在首次访问某个数据的详情页的时候,我们使用thymeleaf给生成静态页面,响应给用户,同时放到指定的nginx代理的目录下,
那么用户在第二次访问这个数据详情页是,通过nginx中的配置,nginx首先会去该目录下查看是否有这个数据静态页面存在,如果存在的话,直接就访问静态html文件了,如果不存在,才会放行到我们微服务里去请求数据。
然后在线上的时候,会结合CDN技术,同时指向nginx代理的静态页面的路径,进一步提高用户的访问速度。
在生成静态页面的同时会造成一个问题,就是数据库的数据修改之后,需要同步到静态页面中去,
这里我们采用的是RabbitMQ做的异步处理,在数据库数据修改之后,通过MQ发送消息,静态化的微服务收到消息之后,会重新生成一遍html页面,这样就会跟数据库的数据同步了
我可以给您详细说下RabbitMQ这里。。。。。
链接:https://www.cnblogs.com/zwtblog/tag/RabbitMQ/
RabbitMQ
地址:https://www.cnblogs.com/zwtblog/tag/RabbitMQ/
1、介绍一下RabbitMQ
RabbitMQ是Erlang语言开发的基于AMQP的一款消息中间件,
核心思想是生产者不会将消息直接发送给队列,消息在发送给客户端时先发送给交换机,然后由交换机转发给对应的队列。对路由(Routing),负载均衡(Load balance)、数据持久化都有很好的支持。
它里边有5种数据传递方式
第一种是简单模型,一个生产者,一个队列,一个消费者,队列只能被一个消费者监听,所以生产者将消息发给队列之后,只能有一个消费者收到消息
第二种是工作模型,一个生产者,一个队列,多个消费者,队列可以被多个消费者监听,但是生产者将消息发给队列之后,还是只能有一个消费者接收到消息
后边三种都叫订阅模型,这三种里边引入了交换机的概念,具体的区分是根据交换机的类型区分的,在这三种模式种,生产者把消息发送给交换机,交换机不负责存储消息,由交换机发送给指定的队列,消费者监听队列消费息。
首先是fanout类型,这种叫广播模式,生产者将消息发送给交换机,
交换机会将消息转发给所有绑定到到当前交换机的队列中,对应监听队列的消费者都能收到消息,但是,如果没有队列绑定到这个交换机,消息会被MQ丢弃。
接着是direct类型,这种叫定向模式,也叫路由模式,这种模式中,
队列在绑定交换机的时候,同时指定了自己的routing key
,可以理解为一个路由标示,
生产者在发送消息给交换机的时候,同时指定要发送给的routing key
,
这时候,交换机就会根据这个routing key
来定向的发送给对应的队列,对应监听该routing key
的队列的消费者就能收到消息,但是如果交换机没有找到对应的routing key
,消息会被丢弃。
最后是topic模式,这种叫通配符模式,队列在绑定交换机的时候,同时指定了自己的routing key
,
生产者在发送消息给交换机的时候,同时指定要发送给的`routing key `的通配符,
一般这个`routing key`是由多个单词用`.`的方式隔开的,
通配符中,`#`号可以匹配一个或者多个单词,`*`号只能匹配一个单词,
例如生产者指定的通配符为`a.#`,可以匹配到的`routing key`有:`a.b`、`a.b.c`等,
如果是`a.*`的话,只能匹配`a.b`或者是`a.c`这样的`routing key`。
每个队列绑定到交换机的时候可以定义多个`routing key`,交换机会跟据指定的通配符,
发送到匹配通配符的`routing key` 对应的队列中,对应的消费者就可以收到消息了,
但是,如果没有符合通配符的`routing key `,消息会被丢弃
当然,在生产中使用的时候,为了避免MQ宕掉等造成消息丢失的问题,我们都会配置持久化措施,
在RabbitMQ里,需要将交换机持和队列和消息持久化,这样消息就会被持久化到磁盘中,不会因为突发的断电等情况导致消息丢失。
2、如何保证消息确定消息发送成功,并且被消费成功,有什么保障措施
首先,需要确保消息被发送成功,RabbitMQ中提供了事物和confirm的机制,
事物的话,就类似于数据库的事物,开启,执行,提交,如果过程中发生任何异常,就会触发回滚机制,
我们可以在回滚中加入一些逻辑处理,重新发送或者日志记录,同时配置生产者确认的机制,
第一种就是消费端配置手动ACK确认机制,消息被消费完成的时候,手动确认告诉MQ消费成功,
MQ才会删除消息,如果消息被接收了,但是MQ没有收到ack确认,那么消息在MQ中会变为unacked状态,
我们可以通过项目日志或者MQ面板监控,当消费者断开连接之后,消息会重新回到队列中,消费者重新连接之后,会再次收到消息。
第二种就是可以结合数据库解决,生产者端发送成功之后,可以在数据库中存储发送的消息和发送时间,
并标记状态为未消费状态,消费者端消费完成之后,标记MySQL中数据的状态为已经消费。
消费者端通过定时任务去数据库跑批检索超时未被消费的消息并重新发送,这种方式可以很好的解决消费失败的问题,
但是增加了生产者和消费者之间的耦合度,以及会造成消息重复消费的问题,
我们可以在保证消息发送成功的基础上,将上述逻辑放在消费者端,生产者正常发送消息,消费者在收到消息之后,存储到MySQL中,标记状态为未消费,同时ACK通知MQ确认,然后再消费消息,消息消费成功之后,改变MySQL状态为已消费,消费端同时配置定时任务跑批检索数据库,定时重新执行超时未消费的消息。
我们项目中的MQ主要用于数据同步使用的,MySQL数据发生变化之后,需要同步到ES索引库以及静态化页面中,这里我们配置了生产者确认模式,和消费者手动ack确认机制。
3、如何保证消息不被重复消费
我们可以在生产者端,发送消息时,数据的变动部分,进行md5加密处理,同时配置上一个唯一id,
每次发送的数据的唯一id是递增的,相当于版本号的功能,在消费者端,收到消息之后,
首先去数据库匹配一下md5值如果数据库中记录的当前记录已经被消费,那么就不进行消费,
如果发现数据库没有记录当前消息,或者记录的状态没有被消费,那么才会重新消费该消息
4、RabbitMQ 宕机了怎么处理
RabbitMQ 提供了持久化的机制,将内存中的消息持久化到硬盘上,
即使重启 RabbitMQ,消息也不会丢失。
持久化队列和非持久化队列的区别是,持久化队列会被保存在磁盘中,固定并持久的存储,
当 Rabbit 服务重启后,该队列会保持原来的状态在RabbitMQ 中被管理,
而非持久化队列不会被保存在磁盘中,Rabbit 服务重启后队列就会消失。
非持久化比持久化的优势就是,由于非持久化不需要保存在磁盘中,所以使用速度就比持久化队列快。
即是非持久化的性能要高于持久化。
而持久化的优点就是消息会一直存在,不会随服务的重启或服务器的宕机而消失。
使用的时候需要根据实际需求来判断具体如何使用。
授权认证
地址:https://www.cnblogs.com/zwtblog/tag/SpringSecurity/
1、授权认证(登陆注册)的逻辑
这块来说就比较简单了,我们提供的注册功能有用户名密码注册,手机号注册,微信登陆绑定注册功能
-
用户名密码注册登陆
我们系统规定的是用户名不得重复,注册的时候,会去做一下重复校验,向后台提交注册信息的时候,密码都会经过md5加密传输,到后台会首先用加密工具生成32位的盐值,然后把用户名通过md5加密之后,用户名的md5和密码的md5和盐值结合之后,生成md5值,然后一起存入数据库,用户登录的时候,按照首先根据用户名去数据库查找用户,找出来用户之后,根据相同的逻辑计算加密之后的密码和数据库的密码对比,对比一致则登陆成功
-
手机号注册登陆(推荐使用,方便快捷)
这里用到了阿里的短信服务功能,注册的时候,手机号码校验通过之后,向用户手机发送验证码,后台将验证码和手机号对应关系存入Redis,用户提交注册之后,验证码跟Redis中对比即可
-
微信登陆绑定系统用户注册
这里结合微信登陆使用的,微信登陆之后,如果发现对应openid没有绑定系统用户,需要提示用户去绑定,然后才能注册
常识:微信开发,有两个平台,
微信开放平台,主要用于app端以及web端扫码登录等开发,app中,微信登陆,微信支付,微信分享等
微信公众平台,主要用于微信网页开发,公众号开发,公众号中网页登陆,微信分享,微信支付等
- web端微信扫码登录
微信开放平台注册开发者帐号,并拥有一个已审核通过的网站应用,并获得相应的AppID和AppSecret。
申请微信登录且通过审核后,可开始接入流程。
就是你的网站要想实现微信扫码登陆功能首先要在微信备案通过,它才会给你个AppID和AppSecret。
1、用户点击网站微信登陆图标。
2、网站带上AppID和回调域名参数请求微信OAuth2.0授权登陆。
3、第二步通过后,微信返回二维码供用户扫码。
4、用户扫码确定授权。
5、微信带上code参数回调java后端回调地址。
6、Java后端获取到Code后,在带上带上AppID和AppSecret和Code再去调微信接口获取access_token。
7、获取access_token后就可以解析用户的一些基本信息,比如:微信用户头像、用户名、性别、城市等一些基本参数。
- 微信app登陆
因为是app登陆,需要用到微信开放平台的接入功能,微信登陆采用的auth2.0的验证机制,在开放平台里注册了账号并通过企业认证,获取了AppID 和 AppSecret之后,第一步我们的服务器先获取code,传给移动端,移动端跟微信交互,用户确认授权之后,然后移动端请求我们后台获取用户信息,我们后台会先去用code获取access_token,然后在通过access_token获取用户信息,同时会返回用户的openId,我们会根据这个openId去我们的数据库查,是否已经获取过用户信息,如果获取过用户信息,看一下最后获取时间,因为每3天更新一次,所以这里会看一下是否需要重新获取如果没有获取过用户信息,会通过access_token获取用户信息,提示绑定系统用户,access_token失效时间为2个小时,因为微信有接口请求次数限制,2小时之内不会再去请求微信的access_token
因为我们系统是微服务架构,所以这里使用JWT实现了单点登录,
因为平台有很多,有web端、管理端,还有一个清结算平台,
实现单点登录会更方便用户使用,登录之后,给用户颁发了一个token令牌,每次请求的时候,都会在请求头里携带这个token,
经过我们的网关的时候,会对这个token做一个权限认证,认证通过,然后才能请求到我们的微服务具体的接口
2、说一下JWT
JWT -- JSON WEB TOKEN
微服务集群中的每个服务,对外提供的都是Restful风格的接口。而Restful风格的一个最重要的规范就是:服务的无状态性,即:
- 服务端不保存任何客户端请求者信息
- 客户端的每次请求必须具备自描述信息,通过这些信息识别客户端身份,一般这里就是token登陆令牌
这样做的话,会有很多好处,比如
- 客户端请求不依赖服务端的信息,任何多次请求不需要必须访问到同一台服务
- 服务端的集群和状态对客户端透明
- 服务端可以任意的迁移和伸缩
- 减小服务端存储压力
我们项目中也是采用的无状态登陆,使用token作为身份认证的令牌,这个token就是使用JWT生成的,JWT类型的token分为三部分,头部,载荷,签名,头部存储一些识别信息,载荷存储一些用户的基本字段,我们存储的是id的username,签名是通过头部和载荷外加RAS非对称加密生成的
3、说一下auth2.0机制
其实就是一种授权机制。数据的所有者告诉系统,同意授权第三方应用进入系统,获取这些数据。
系统从而产生一个短期的进入令牌(token),用来代替密码,供第三方应用使用。
令牌(token)与密码(password)的作用是一样的,都可以进入系统,但是还是有一些差异的。
(1)令牌是短期的,到期会自动失效,用户自己无法修改。密码一般长期有效,用户不修改,就不会发生变化。
(2)令牌可以被数据所有者撤销,会立即失效。以上例而言,屋主可以随时取消快递员的令牌。密码一般不允许被他人撤销。
(3)令牌有权限范围(scope),比如只能进小区的二号门。对于网络服务来说,只读令牌就比读写令牌更安全。密码一般是完整权限。
有了这些设计,保证了令牌既可以让第三方应用获得权限,同时又随时可控,不会危及系统安全。这就是 OAuth 2.0 的优点。
注意,只要知道了令牌,就能进入系统。系统一般不会再次确认身份,所以令牌必须保密,泄漏令牌与泄漏密码的后果是一样的。这也是为什么令牌的有效期,一般都设置得很短的原因。
具体来说,auth2.0一共分成四种授权类型
第一种是授权码模式,这种方式是最常用的流程,安全性也最高,它适用于那些有后端的 Web 应用。
授权码通过前端传送,令牌则是储存在后端,而且所有与资源服务器的通信都在后端完成。这样的前后端分离,可以避免令牌泄漏。
比如A应用想要获取B应用的用户数据,
A会首先访问B的授权页面,用户点击确认授权之后,
B会请求A配置的回调地址,同时附带上授权码code,
A可以通过这个code向B申请登录令牌access_token,获取到登录令牌之后,然后就可以向B应用请求用户信息数据了。
最常见的就是微信登陆等第三方登录都用的这种方式。
还有就是隐藏式、密码式、客户端凭证等三种模式。
Nginx
地址:https://www.cnblogs.com/zwtblog/tag/Nginx/
1、介绍一下nginx
Nginx 是一个高性能的 HTTP 和反向代理服务器,具有反向代理和负载均衡以及动静分离等功能
反向代理功能
反向代理是指以代理服务器来接受用户的请求,
然后将请求,分发给内部网络上的服务器,并将从服务器上得到的结果返回给用户,此时代理服务器对外就表现为一个反向代理服务器。
反向代理总结就一句话就是:代理端代理的是服务端。
反向代理的话,只需要配置对应的server模块就行了,里面配置上server_name和对应监听的端口,然后在配置location路径转发规则就行,当然也可以配置代理静态资源。
负载均衡吧,负载均衡即是代理服务器将接收的请求均衡的分发到各服务器中,
负载均衡主要解决网络拥塞问题,提高服务器响应速度,服务就近提供,达到更好的访问质量,减少后台服务器大并发压力,同时还可以保证服务端的高可用。
常用的nginx的负载均衡策略有轮询、权重、IP Hash、最少连接数,默认的策略就是轮询。
负载均衡这里需要配置upstream模块,在upstream中配置好当前的负载均衡规则,然后配置好每个server对应的ip端口即可,在server模块配置的转发规则就时向这个upstream上转发就行了
2、nginx如何处理http请求
Nginx这块的处理时结合多进程机制和异步机制 ,异步机制使用的是异步非阻塞方式
首先呢是多进程机制
服务器每当收到一个客户端请求时,就有服务器主进程 ( master process )生成一个 子进程( worker process )出来和客户端建立连接进行交互,直到连接断开,该子进程就结束了。
使用进程的好处是各个进程之间相互独立,不需要加锁,减少了使用锁对性能造成影响,同时降低编程的复杂度,降低开发成本。
接着呢就是采用独立的进程,可以让进程互相之间不会影响 ,如果一个进程发生异常退出时,其它进程正常工作, master 进程则很快启动新的 worker 进程,确保服务不会中断,从而将风险降到最低。
缺点是操作系统生成一个子进程需要进行 内存复制等操作,在资源和时间上会产生一定的开销。
当有大量请求时,会导致系统性能下降
还有就是异步非阻塞机制
每个工作进程 使用 异步非阻塞方式 ,可以处理多个客户端请求 。
当某个工作进程接收到客户端的请求以后,调用 IO 进行处理,如果不能立即得到结果,就去 处理其他请求 (这就是非阻塞 )。
而 客户端 在此期间也 无需等待响应 ,可以去处理其他事情(这就是异步 )。
当 IO 返回时,就会通知此工作进程 ;该进程得到通知,暂时挂起 当前处理的事务去响应客户端请求 。
3、Nginx常用命令
启动:nginx
当我们修改配置了之后,重启:nginx -s reload
停止nginx:nginx -s stop
4、什么是动静分离,为什么要动静分离
这个我理解的来说就是把前端静态资源和后台请求分离开,主要就是为了提升静态资源的访问速度,
一般前后端分离的项目用的居多,分离之后,我们可以把静态资源放入CDN中去,可以实现用户的就近访问,同时还提供了更大的带宽。
5、如何保证Nginx高可用
因为Nginx是我们所有项目的入口,必须要保证Nginx的高可用,这里一般都用的Nginx集群,并同时加入了keep-alive来做的双机热备处理,
通过请求一个vip(virtual ip:虚拟ip)来达到请求真实IP地址的功能,而VIP能够在一台机器发生故障时候,自动漂移到另外一台机器上,从来达到了高可用的功能。
Redis
地址:https://www.cnblogs.com/zwtblog/tag/Redis/
1、介绍一下Redis
Redis是一个非关系数据库,我们项目中主要用它来存储热点数据的,减轻数据库的压力,单线程纯内存操作,采用了非阻塞IO多路复用机制,就是单线程监听,我们项目中使用SpringData-Redis来操作Redis
我们项目中使用Redis的地方很多,比方说首页的热点数据,数据字典里的数据等都用热地说存储来提高访问速度
Redis呢有5种数据类型,string、list、hash、set、zset,我们常用的有string、list和hash,
一些简单的key-value类型的都存储在string类型中,比如一些系统开关之类的,是否开放注册等,还有一些存储在hash中,比如我们的首页的推荐数据和热门数据,都是用hash来存储的,一个固定的字符串作为key,每条数据的id作为field,对应的数据作为value存储
Redis还有两种持久化方式,一个是RDB,这也是Redis默认的持久化方式,这种方式是以快照的方式存储数据,
在固定的时间段内如果有多少变化,那么就会生成快照存储到磁盘上,
Redis 在进行数据持久化的过程中,会先将数据写入到一个临时文件中,待持久化过程都结束了,才会用这个临时文件替换上次持久化好的文件。
正是这种特性,让我们可以随时来进行备份,因为快照文件总是完整可用的。
对于 RDB 方式,Redis 会单独创建一个子进程来进行持久化,而主进程是不会进行任何 IO 操作的,这样就确保了 Redis 极高的性能。
这种方式的优点呢就是快,但是如果没等到持久化开始Redis宕机了,那么就会造成数据丢失
还有一种是AOF,是即时性的持久化方式,是将 Redis 执行过的所有写指令记录下来,在下次 Redis 重新启时,只要把这些写指令从前到后再重复执行一遍,就可以实现数据恢复了。AOF的方式会导致性能下降
两种方式可以同时开启,当两种方式同时开启时,数据恢复Redis会优先选择AOF恢复。
我们项目中使用的持久化方式就是默认的RDB,因为我们存储的数据首先来说不是很重要的数据,如果丢失了,还可以从数据库加载到,主要用的就是性能这块。
2、Redis缓存雪崩和缓存穿透、缓存预热、缓存降级
- 缓存雪崩
我们可以简单的理解为:由于原有缓存失效,新缓存还没有存入到Redis的期间。
解决办法:
加最多的解决方案就是锁,或者队列的方式来保证不会有大量的线程对数据库一次性进行读写,从而避免失效时大量的并发请求落到底层存储系统上。
还有一个简单方案就是缓存失效时间分散开,不设置固定的实效时间,采用随机失效的策略来解决。
- 缓存穿透:
缓存穿透是指用户查询数据,在数据库没有,自然在缓存中也不会有。
这样就导致用户查询的时候,在缓存中找不到,每次都要去数据库再查询一遍,然后返回空,这就相当于进行了两次无用的查询。
像这样请求就绕过缓存直接查数据库,这也是经常提的缓存命中率问题
解决办法:
最常见的则是采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的BitMap中,一个一定不存在的数据会被这个BitMap拦截掉,从而避免了对底层存储系统的查询压力。
另外也有一个更为简单粗暴的方法,如果一个查询返回的数据为空,不管是数据不存在,还是系统故障,我们仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。
- 缓存预热:
缓存预热就是系统上线后,将相关的缓存数据直接加载到缓存系统。
这样就可以避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题!
用户直接查询事先被预热的缓存数据!
操作方式:
1、直接写个缓存刷新页面,上线时手工操作下;
2、数据量不大,可以在项目启动的时候自动进行加载;
然后就是缓存更新:
1、定时去清理过期的缓存;
2.、当有用户请求过来时,再判断这个请求所用到的缓存是否过期,过期的话就去底层系统得到新数据并更新缓存
- 缓存降级:
当访问量剧增、服务出现问题,比如响应时间慢或不响应,或者非核心服务影响到核心流程的性能时,仍然需要保证服务还是可用的,即使是有问题的服务。
Redis可以帮助系统实现数据降级载体,系统可以根据一些关键数据进行自动降级,也可以配置开关实现人工降级。降级的最终目的是保证核心服务可用,即使是有损的。
3、Redis分布式锁
这个分布式锁这里,我们原来传统的项目都在单台服务器上部署用Java里的锁synchronized这个同步锁就行,
但是他这个是针对对象的锁,但是我们分布式的项目需要把项目部署到多台服务器上,每台服务器的对象都不同,
所以就得考虑用分布式锁,这块实现起来也比较简单,其实这个锁就是Redis中的一个key-value的一对值,在使用的时候吧,
首先使用setnx方法进行尝试加锁,并可以设置过期时间,如果返回1则代表加锁成功,然后立即对这个锁设置一个实效时间,防止服务宕机,锁一致存在,在处理完业务逻辑之后,删除锁就行了,其他线程就可以获取锁进行业务了
4、Redis主从复制
通过持久化功能,Redis保证了即使在服务器重启的情况下也不会损失(或少量损失)数据,因为持久化会把内存中数据保存到硬盘上,重启会从硬盘上加载数据。
但是由于数据是存储在一台服务器上的,如果这台服务器出现硬盘故障等问题,也会导致数据丢失。
为了避免单点故障,通常的做法是将数据库复制多个副本以部署在不同的服务器上,这样即使有一台服务器出现故障,其他服务器依然可以继续提供服务。
为此, Redis 提供了复制功能,可以实现当一台数据库中的数据更新后,自动将更新的数据同步到其他数据库上。
Redis的主从结构可以采用一主多从或者级联结构,Redis主从复制可以根据是否是全量分为全量同步和增量同步,配置非常简单,只需要在从节点配置slave of
主节点的IP即可,如果有密码,还需要配置上密码,从节点只能读数据,不能写数据。
全量同步主要发生在初次同步的时候,大概的步骤是
- 从服务器连接主服务器,发送SYNC命令;
- 主服务器接收到SYNC命名后,开始执行BGSAVE命令生成RDB文件并使用缓冲区记录此后执行的所有写命令;
- 主服务器BGSAVE执行完后,向所有从服务器发送快照文件,并在发送期间继续记录被执行的写命令;
- 从服务器收到快照文件后丢弃所有旧数据,载入收到的快照;
- 主服务器快照发送完毕后开始向从服务器发送缓冲区中的写命令;
- 从服务器完成对快照的载入,开始接收命令请求,并执行来自主服务器缓冲区的写命令;
还有就是增量同步,主要发生在Redis的工作过程中,Slave初始化后开始正常工作时主服务器发生的写操作同步到从服务器的过程。
增量复制的过程主要是主服务器每执行一个写命令就会向从服务器发送相同的写命令,从服务器接收并执行收到的写命令。
5、Redis集群
Redis本身就支持集群操作Redis_cluster,集群至少需要3主3从,且每个实例使用不同的配置文件,主从不用配置,集群会自己选举主数据库和从数据库,为了保证选举过程最后能选出leader,
就一定不能出现两台机器得票相同的僵局,所以一般的,要求集群的server数量一定要是奇数,也就是2n+1台,并且,如果集群出现问题,其中存活的机器必须大于n+1台,否则leader无法获得多数server的支持,系统就自动挂掉。
所以一般是3个或者3个以上的奇数节点。
Redis 2.8中提供了哨兵工具来实现自动化的系统监控和故障恢复功能。
哨兵的作用就是监控Redis主、从数据库是否正常运行,主数据库出现故障自动将从数据库转换为主数据库
我们公司搭建的Redis集群是用的Ruby脚本配合搭建的,我们一共搭建了6台服务器,3主3备,
他们之间通信的原理是有一个乒乓协议进行通信的,我再给你说下一他们往里存储数据的机制吧,其实这个Redis搭建好集群以后每个节点都存放着一个hash槽,每次往里存储数据的时候,
Redis都会根据存储进来的key值算出一个hash值,通过这个hash值可以判断到底应该存储到哪一个哈希槽中,取的时候也是这么取的,这就是我了解的Redis集群
6、除了Redis,还了解哪些别的非关系型数据库
有Memacache,MongoDB这些,以及Redis这几个都是非关系型数据库
Memacache是纯内存型的,只支持简单的字符串数据,并且value值最大只能是1MB,而且所有的数据都只能存储在内存中,如果服务宕机或者关机重启,数据就会丢失,没有持久化功能
MongoDB的话是存储的数据都在磁盘上,功能比较多,不过性能没有其他两种好
而Redis呢,支持的数据类型比较多,而且速度也非常快,value最大可以支持到512MB,而且既可以把数据存储在内存里,也可以持久化到磁盘上,重启之后还可以把磁盘中的数据重新加载到内存里,从性能以及数据安全上来说,都比Memacache和MongoDB好一些。
7、Redis数据同步
这一块主要是跟MySQL数据同步吧,MySQL数据可能会发生变动,那么Redis就要跟数据库的数据保持一致我们实际去使用的时候,
是在数据发生变动的地方,比如增删改的时候,新奇一个线程,然后将变动的数据更新到Redis中,根据不同的场景需求,
也可以在数据变动时,把Redis里的数据删掉,下一次用户查询的时候,发现Redis中没有数据,就会重新去数据库加载一遍,这样也可以实现同步的效果
8、介绍一下Redis的pipeline
pipeline的话,就是可以批量执行请求的命令
我们都知道Redis是单线程的,在执行命令的时候,其他客户端是阻塞状态的,如果在高并发的时候,其实是会影响一定效率的,
所以Redis提供了pipeline,可以让我们批量执行命令,大大的减少了IO阻塞以及访问效率
因为pipeline是批量执行命令,我们一般会结合Redis的事物去使用,它也符合事务的ACID特性,
MULTI开启事物,EXEC执行,DISCARD清除事物状态,回到非事物状态,
在使用前,还可以结合WATCH监控来使用,如果我们在执行一组命令的过程中,不想让其中的某个值被其他客户端改动,就可以使用WATCH,
使用了之后,如果被改动,事务会自动回滚,可以很好的保证我们批量执行命令的时候,数据的准确性,在使用完成之后,可以使用UNWATCH解除监控。
9、介绍下Redis中key的过期策略
定时删除:在设置key的过期时间的同时,为该key创建一个定时器,让定时器在key的过期时间来临时,对key进行删除
惰性删除:key过期的时候不删除,每次从数据库获取key的时候去检查是否过期,若过期,则删除,返回null
定期删除:每隔一段时间执行一次删除过期key操作
Redis 过期策略是:定期删除+惰性删除。
所谓定期删除,指的是 Redis 默认是每隔 100ms 就随机抽取一些设置了过期时间的 key,检查其是否过期,如果过期就删除。
前端
1、介绍下VUE(如果分开问你的话,就分开说)
Vue呢在我的理解中,就是一个轻量级的前端JS框架,最重要的就是它的MVVM思想,
就是model和view之间可以通过view-model监听来进行数据到视图之间,以及视图到数据的双向绑定操作,
我们在写前端代码的过程中,不需要关注任何dom操作,比jQuery方便很多
Vue常用的指令都有v-for
,v-if
,v-show
等,绑定事件用v-on
,可以省略为@
符号,绑定属性用v-bind
,可以省略为冒号,
还有就是Vue的生命周期,常用的基本上就是created
这个钩子函数,在Vue组件创建之后,就会调用它,我们都在这里去服务端初始化数据,
有的时候看需求了,如果需要页面大致框架渲染出来之后,再加载数据的话,就用mounted
这个钩子函数里加载就行了,它相当于原生JS里的onload方法。
另外的话,就是基本上开发的时候,都用的是组件开发,经常的话会涉及到组件之间的传递数据以及传方法,传数据的话就使用动态绑定属性的方式,子组件中定义props
就可以接收了,但是子组件无法直接修改父组件中数据模型的值,只能通过调用父级方法来修改,这就需要父组件给子组件传方法,通过@
符号,自定义属性名就可以把父级中的方法传递给子组件,子组件调用的时候,使用$emit
就可以调用这个方法。
另外一般vue项目都是通过npm管理的,npm呢就相当于与前端的maven,主要是帮助我们管理JS依赖的,我们在想要添加JS依赖的时候,比如axios,可以通过npm install -g exios
命令来下载就行了,
也不需要我们从网上手动下载了,另外结合WebPack打包的工具,在开发以及部署的时候方便很多了,不过这些都是我们专门的前端工程师来做的,
用Vue的脚手架搭建框架,以及配置路由等等,我们实际去写的时候,都是往里边填代码,我这边平时就是好琢磨,就研究了一下,现在来说,简单的框架搭建,以及npm管理来说,都不是问题。
2、说说你了解过的UI框架
符合Vue风格的MVVM思想的,最常见的就是ElementUI
,还有Vuetify
、iview
等
符合jQuery风格的手动dom元素操作的,有BootStrap
、LayUi
以及跟早的EasyUi
等
还有现在非常火的移动端的开发框架,Flutter
,它是google公司推出的开源免费的移动端的UI框架
3、jQuery元素选择器都有哪些
$("#id")
$(".clazz")
$("标签名")
Spring
地址:https://www.cnblogs.com/zwtblog/tag/Spring/
1、介绍一下spring
关于Spring的话,我们平时做项目一直都在用,不管是使用SSH还是使用SSM,都可以整合。Spring里面主要的就三点,也就是核心思想,IOC控制反转,DI依赖注入,AOP切面编程
我先来说说IOC吧,IOC就是spring里的控制反转,把类的控制权呢交给spring来管理,
我们在使用的时候,在spring的配置文件中,配置好bean标签,以及类的全路径,如果有参数,然后在配置上相应的参数。
这样的话,spring就会给我们通过反射的机制实例化这个类,同时放到spring容器当中去。
我们在使用的时候,需要结合DI依赖注入使用,把我们想使用的类注入到需要的地方就可以,
依赖注入的方式有构造器注入、getset注入还有注解注入。我们现在都使用@autowired
或者@Resource
注解的方式注入。
然后就是AOP切面编程,他可以在不改变源代码的情况下对代码功能的一个增强。
我们在配置文件中配置好切点,然后去实现切面的逻辑就可以实现代码增强,这个代码增强,包括在切点的执行前,执行中,执行后都可以进行增强逻辑处理,
不用改变源代码,这块我们项目中一般用于权限认证、日志、事务处理这几个地方。
2、AOP的实现原理
这块呢,我看过spring的源码,底层就是动态代理来实现的,所谓的动态代理就是说 AOP 框架不会去修改字节码,而是每次运行时在内存中临时为方法生成一个 AOP 对象,
这个 AOP 对象包含了 ,目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。
Spring AOP 中的动态代理主要有两种方式,JDK 动态代理和 CGLIB 动态代理:
-
JDK 动态代理只提供接口的代理,不支持类的代理。核心InvocationHandler 接口和 Proxy 类,InvocationHandler 通过 invoke()方法反射来调用目标类中的代码,动态地将横切逻辑和业务编织在一起;
接着,Proxy 利用InvocationHandler 动态创建一个符合接口的的实例,生成目标类的代理对象。
-
如果代理类没有实现 InvocationHandler 接口,那么 Spring AOP 会选择使用 CGLIB 来动态代理目标类。CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成指定类的一个子类对象,并覆盖其中特定方法并添加增强代码,从而实现 AOP。
CGLIB 是通过继承的方式做的动态代理,因此如果某个类被标记为 final,那么它是无法使用 CGLIB 做动态代理的。
不过在我们的业务场景中没有代理过final的类,基本上都代理的Controller层实现权限以及日志,还有就是Service层实现事务统一管理
3、详细介绍下IOC容器
Spring 提供了两种 IOC 容器,分别为 BeanFactory 和 ApplicationContext
BeanFactory 是基础类型的 IOC 容器,提供了完整的 IOC 服务支持。
简单来说,BeanFactory 就是一个管理 Bean 的工厂,它主要负责初始化各种 Bean,并调用它们的生命周期方法。
ApplicationContext 是 BeanFactory 的子接口,也被称为应用上下文。
它不仅提供了 BeanFactory 的所有功能,还添加了对 i18n(国际化)、资源访问、事件传播等方面的良好支持。
他俩的主要区别在于,如果 Bean 的某一个属性没有注入,则使用 BeanFacotry 加载后,在第一次调用 getBean() 方法时会抛出异常,但是呢ApplicationContext 会在初始化时自检,这样有利于检查所依赖的属性是否注入。
因此,在实际开发中,通常都选择使用 ApplicationContext
4、@Autowired
和 @Resource
的区别
@Autowired` 默认是按照类型注入的,如果这个类型没找到,会根据名称去注入,如果在用的时候需要指定名称,可以
加注解`@Qualifier("指定名称的类")
@Resource`注解也可以从容器中注入bean,默认是按照名称注入的,如果这个名称的没找到,就会按照类型去找,
也可以在注解里直接指定名称`@Resource(name="类的名称")
5、SpringBean的生命周期
生命周期这块无非就是从创建到销毁的过程
Spring容器可以管理 Singleton 作用域 Bean 的生命周期,在此作用域下,Spring 能够精确地知道该 Bean 何时被创建,何时初始化完成,以及何时被销毁。
而对于 prototype 作用域的 Bean,Spring 只负责创建,当容器创建了 Bean 的实例后,Bean 的实例就交给客户端代码管理,Spring 容器将不再跟踪其生命周期。
每次客户端请求 prototype 作用域的 Bean 时,Spring 容器都会创建一个新的实例,并且不会管那些被配置成prototype 作用域的 Bean 的生命周期。
整体来说就4个步骤:实例化bean,属性赋值,初始化bean,销毁bean
- 首先就是实例化bean,容器通过获取BeanDefinition对象中的信息进行实例化
- 然后呢就是属性赋值,利用依赖注入完成 Bean 中所有属性值的配置注入
- 接着就是初始化bean,如果在配置文件中通过 init-method 属性指定了初始化方法,则调用该初始化方法。
- 最后就是销毁bean,和init-method一样,通过给destroy-method指定函数,就可以在bean销毁前执行指定的逻辑
6、springbean的作用域
Spring 容器中的 Bean 可以分为 5 个范围:
(1)singleton:单例模式,使用 singleton 定义的 Bean 在 Spring 容器中只有一个实例,这也是 Bean 默认的作用域。 controller、service、dao层基本都是singleton的
(2)prototype:原型模式,每次通过 Spring 容器获取 prototype 定义的 Bean 时,容器都将创建一个新的 Bean 实例。
(3)request:在一次 HTTP 请求中,容器会返回该 Bean 的同一个实例。而对不同的 HTTP 请求,会返回不同的实例,该作用域仅在当前 HTTP Request 内有效。
(4)session:在一次 HTTP Session 中,容器会返回该 Bean 的同一个实例。而对不同的 HTTP 请求,会返回不同的实例,该作用域仅在当前 HTTP Session 内有效。
(5)global-session:全局作用域,在一个全局的 HTTP Session 中,容器会返回该 Bean 的同一个实例。
7、事务的传播特性
解读:事务的传播特性发生在事务方法与非事物方法之间相互调用的时候,
在事务管理过程中,传播行为可以控制是否需要创建事务以及如何创建事务
属性名称 | 值 | 描 述 |
---|---|---|
PROPAGATION_REQUIRED | required | 支持当前事务。如果 A 方法已经在事务中,则 B 事务将直接使用。否则将创建新事务,默认就是这个 |
PROPAGATION_SUPPORTS | supports | 支持当前事务。如果 A 方法已经在事务中,则 B 事务将直接使用。否则将以非事务状态执行 |
PROPAGATION_MANDATORY | mandatory | 支持当前事务。如果 A 方法没有事务,则抛出异常 |
PROPAGATION_REQUIRES_NEW | requires_new | 将创建新的事务,如果 A 方法已经在事务中,则将 A 事务挂起 |
PROPAGATION_NOT_SUPPORTED | not_supported | 不支持当前事务,总是以非事务状态执行。如果 A 方法已经在事务中,则将其挂起 |
PROPAGATION_NEVER | never | 不支持当前事务,如果 A 方法在事务中,则抛出异常 |
PROPAGATION.NESTED | nested | 嵌套事务,底层将使用 Savepoint 形成嵌套事务 |
8、事务的隔离级别
Spring 事务的本质其实就是数据库对事务的支持,没有数据库的事务支持,spring是无法提供事务功能的。
真正的数据库层的事务提交和回滚是通过 binlog实现的。隔离级别有四种
- Read uncommitted (读未提交):读未提交,允许另外一个事务可以看到这个事务未提交的数据,最低级别,任何情况都无法保证。
- Read committed (读已提交):保证一个事务修改的数据提交后才能被另一事务读取,而且能看到该事务对已有记录的更新,可避免脏读的发生。
- Repeatable read (可重复读):保证一个事务修改的数据提交后才能被另一事务读取,但是不能看到该事务对已有记录的更新,可避免脏读、不可重复读的发生。
- Serializable (串行化):一个事务在执行的过程中完全看不到其他事务对数据库所做的更新,可避免脏读、不可重复读、幻读的发生。
9、spring中都用了哪些设计模式
(1)工厂模式:BeanFactory就是简单工厂模式的体现,用来创建对象的实例;
(2)单例模式:Bean默认为单例模式。
(3)代理模式:Spring的AOP功能用到了JDK的动态代理和CGLIB字节码生成技术;
(4)模板方法:用来解决代码重复的问题。比如. RestTemplate, JmsTemplate, JpaTemplate,XXXTemplate
(5)观察者模式:定义对象键一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知被制动更新,如Spring中listener的实现–ApplicationListener。
10、Spring中如何处理Bean在线程并发时线程安全问题
在一般情况下,只有无状态的 Bean 才可以在多线程环境下共享,
在 Spring 中,绝大部分 Bean 都可以声明为 singleton 作用域,因为 Spring 对一些 Bean 中非线程安全状态采用 ThreadLocal 进行处理,解决线程安全问题。
ThreadLocal 和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。
同步机制采用了“时间换空间”的方式,仅提供一份变量,不同的线程在访问前需要获取锁,没获得锁的线程则需要排队。
而 ThreadLocal 采用了“空间换时间”的方式。
ThreadLocal 会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。
因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。
ThreadLocal 提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的变量封装进 ThreadLocal。
我们项目中的拦截器里就有这样的逻辑,在我们微服务中,网关进行登录以及鉴权操作,
具体的微服务中需要用到token去解析用户信息,我们就在拦截器的preHandler里定义了threadlocal,通过token解析出user的信息,
后续controller以及service使用的时候,直接从threadlocal中取出用户信息的,在拦截器的afterCompletion方法中清理ThreadLocal 中的变量,避免变量堆积消耗内存
SpringMVC
1、介绍一下springMVC
springmvc是一个视图层框架,通过MVC模型让我们很方便的接收和处理请求和响应。
它的核心控制器是DispatcherServlet,他的作用是接收用户请求,然后给用户反馈结果。
它的作用相当于一个转发器或中央处理器,控制整个流程的执行,对各个组件进行统一调度,以降低组件之间的耦合性,有利于组件之间的拓展
接着就是处理器映射器(HandlerMapping):他的作用是根据请求的URL路径,通过注解或者XML配置,寻找匹配的处理器信息。
还有就是处理器适配器(HandlerAdapter):他的作用是根据映射器处理器找到的处理器信息,按照特定执行链路规则执行相关的处理器,返回ModelAndView
最后是视图解析器(ViewResolver):他就是进行解析操作,通过ModelAndView对象中的View信息将逻辑视图名解析成真正的视图View返回给用户
2、springMVC的执行流程
(1)用户发送请求至前端控制器 DispatcherServlet;
(2) DispatcherServlet 收到请求后,调用 HandlerMapping 处理器映射器,请求获取 Handle;
(3)处理器映射器根据请求 url 找到具体的处理器,生成处理器对象及处理器拦截器(如果有则生成)一并返回给 DispatcherServlet;
(4)DispatcherServlet 调用 HandlerAdapter 处理器适配器;
(5)HandlerAdapter 经过适配调用 具体处理器(Handler,也叫后端控制器);
(6)Handler 执行完成返回 ModelAndView;
(7) HandlerAdapter 将 Handler 执 行 结 果 ModelAndView 返 回 给DispatcherServlet;
(8)DispatcherServlet 将 ModelAndView 传给 ViewResolver 视图解析器进行解析;
(9)ViewResolver 解析后返回具体 View;
(10)DispatcherServlet 对 View 进行渲染视图(即将模型数据填充至视图中)
(11)DispatcherServlet 响应用户。
3、SpringMVC接收前台参数的几种方式
- 1、如果传递参数的时候,通过ur1拼接的方式,直接拿对象接收即可,或者string、 int
- 2、如果传递参数的时候,传到后台的是js对象,那么必须使用对象接收,并且加@requestBody,使用requestBody之后,传递的参数至少要有一个,并且所有传的参数都要在后台对象里存在
- 3、get的请求方式,所有的参数接收都使用普通对象或者string、int
- 4、在用form表单提交的时候,所有的参数接收都使用普通对象或者string、int
4、SpringMVC中的常用注解
@RequestMapping:指定类或者方法的请求路径,可以使用method字段指定请求方式
@GetMapping、@PostMapping:规定了请求方式的方法的请求路径
@RequestParam:接收单一参数的
@PathVariable:用于从路径中接收参数的
@CookieValue:用于从cookie中接收参数的
@RequestBody:用于接收js对象的,将js对象转换为Java对象
@ResponseBody:返回json格式数据
@RestController:用在类上,等于@Controller+@ResourceBody两个注解的和,一般在前后端分离的项目中只写接口时经常使用,标明整个类都返回json格式的数据
5、Spring如何整合SpringMVC
简单的说 SpringMVC在SSM中整合 就是 在 web.xml 里边配置springMVC的核心控制器:DispatcherServlet。
它就是对指定后缀进行拦截。
然后在springMVC.xml里边配置扫描器,可以扫描到带@controller注解的这些类,现在用springMVC都是基与注解式开发, 像@service,@Repository @Requestmapping,@responsebody 这些注解标签 等等 都是开发时用的,每个注解标签都有自己的作用。
它还配置一个视图解析器,主要就是对处理之后的跳转进行统一配置,有页面的路径前缀和文件后缀 ,如果有上传相关的设置,还需要配置上multpart的一些配置,比如单个文件最大的大小,以及最大请求的大小。
MyBatis
地址:https://www.cnblogs.com/zwtblog/tag/MyBatis/
1、介绍一下MyBatis,说一下它的优点和缺点是什么?
MyBatis是一个半ORM(对象关系映射)的持久层框架,它内部封装了JDBC,开发时只需要关注SQL语句本身,不需要花费精力去处理加载驱动、创建连接、创建statement等繁杂的过程,使用时直接编写原生态sql。
优点:
1:基于SQL语句编程,相当灵活,不会对应用程序或者数据库的现有设计造成任何影响,SQL写在XML 里,解除sql与程序代码的耦合,便于统一管理,提供XML标签,支持编写动态SQL语句,并可重用;
2:很好的与各种数据库兼容;
3:提供映射标签,支持对象与数据库的ORM字段关系映射,提供对象关系映射标签,支持对象关系组件维护。
4:与JDBC相比,消除了JDBC大量冗余的代码,不需要手动开关连接,能够与Spring很好的集成
缺点:
1:SQL语句的编写工作量较大,尤其当字段多、关联表多时,对开发人员编写SQL语句的功底有一定要求;
2:SQL语句依赖于数据库,导致数据库移植性差,不能随意更换数据库;
2、MyBatis与Hibernate有哪些不同?
首先Hibernate是一个完全面向对象的持久层框架,MyBatis是一个半自动化的持久层框架。
开发方面: Hibernate开发中,sql语句已经被封装,直接可以使用,加快系统开发, MyBatis 属于半自动化,sql需要手工完成,稍微繁琐,但是如果对于庞大复杂的系统项目来说,复杂的sql语句较多,选择Hibernate就不是一个好方案。
sql优化方面:Hibernate 自动生成sql,有些语句较为繁琐,会多消耗一些性能, MyBatis 手动编写sql,可以避免不需要的查询,提高系统性能;
对象管理方面:Hibernate 是完整的对象-关系映射的框架,开发工程中,无需过多关注底层实现,只要去管理对象即可;MyBatis 需要自行管理 映射关系。
缓存方面:Hibernate的二级缓存配置在SessionFactory生成的配置文件中进行详细配置,然后再在具体的表-对象映射中配置是那种缓存,
MyBatis的二级缓存配置都是在每个具体的表-对象映射中进行详细配置,这样针对不同的表可以自定义不同的缓存机制。
总之:MyBatis 小巧、方便、高效、简单、直接、半自动化;Hibernate 强大、方便、高效、复杂、间接、全自动化
3、#{}和${}的区别是什么?
#{}
是预编译处理,${}
是字符串替换。
MyBatis在处理#{}
时,会将sql中的#{}
替换为?号,调用PreparedStatement
的set方法来赋值;
MyBatis在处理${}
时,就是把${}
替换成变量的值。
使用#{}
可以有效的防止SQL注入,提高系统安全性。
4、当实体类中的属性名和表中的字段名不一样 ,怎么办 ?
第一种方法:通过在查询的sql语句中定义字段名的别名,让字段名的别名和实体类的属性名一致;
第二种方法:通过``来映射字段名和实体类属性名的一一对应的关系;
第三种方法:在实体类通过@Column注解也可以实现;
5、通常一个Xml映射文件,都会写一个Dao接口与之对应,这个Dao接口的工作原理是什么?Dao接口里的方法,参数不同时,方法能重载吗?
Dao接口即Mapper接口。
接口的全限名,就是映射文件中的namespace的值;
接口的方法名,就是映射文件中Mapper的Statement的id值;
接口方法内的参数,就是传递给sql的参数。
Mapper接口是没有实现类的,当调用接口方法时,接口全限名+方法名拼接字符串作为key值,可唯一定位一个MapperStatement。
Mapper接口里的方法,是不能重载的,因为是使用 全限名+方法名 的保存和寻找策略。
Mapper 接口的工作原理是JDK动态代理,MyBatis运行时会使用JDK动态代理为Mapper接口生成代理对proxy,
代理对象会拦截接口方法,转而执行MapperStatement所代表的sql,然后将sql执行结果返回。
6、MyBatis 如何执行批量插入?
有两种方式,
第一种就是普通的xml中insert语句可以写成单条插入,在调用方循环N次.
第二种是xml中insert语句写成一次性插入一个N条的list,举例下面的list标签
<insert id="insertBatch" >
insert into person ( <include refid="Base_Column_List" /> )
values
<foreach collection="list" item="item" index="index" separator=",">
(null,#{item.name},#{item.sex},#{item.address})
</foreach>
</insert>
7、MyBatis 如何获取自动生成的(主)键值?
insert 方法总是返回一个int值 ,这个值代表的是插入的行数,不是插入返回的主键id值;
如果采用自增长策略,自动生成的键值在 insert 方法执行完后可以被设置到传入的参数对象中,需要增加两个属性,usegeneratedkeys=”true” keyproperty=”id”
<insert id=”insertname” usegeneratedkeys=”true” keyproperty=”id”>
insert into names (name) values (#{name})
</insert>
8、在mapper中如何传递多个参数?
(1)第一种:
//DAO层的函数
Public UserselectUser(String name,String area);
//对应的xml,#{0}代表接收的是dao层中的第一个参数,#{1}代表dao层中第二参数,更多参数一致往后加即可。
<select id="selectUser"resultMap="BaseResultMap">
select * fromuser_user_t whereuser_name = #{0} anduser_area=#{1}
</select>
(2)第二种: 使用 @param 注解:
public interface usermapper {
user selectuser(@param(“username”) string username,@param(“hashedpassword”) string hashedpassword);
}
然后,就可以在xml像下面这样使用(推荐封装为一个map,作为单个参数传递给mapper):
<select id=”selectuser” resulttype=”user”>
select id, username, hashedpassword
from some_table
where username = #{username}
and hashedpassword = #{hashedpassword}
9、MyBatis有哪些动态sql?
MyBatis动态sql可以在Xml映射文件内,以标签的形式编写动态sql,执行原理是根据表达式的值 完成逻辑判断并动态拼接sql的功能。
MyBatis提供了9种动态sql标签:trim | where | set | foreach | if | choose | when | otherwise | bind
10、Xml映射文件中,除了常见的select|insert|updae|delete标签之外,还有哪些标签?
加上动态sql的9个标签,其中为sql片段标签,通过
标签引入sql片段,为不支持自增的主键生成策略标签。
11、MyBatis的Xml映射文件中,不同的Xml映射文件,id是否可以重复?
不同的Xml映射文件,如果配置了namespace,那么id可以重复;
如果没有配置namespace,那么id不能重复;
原因就是namespace+id是作为Map<String, MapperStatement>的key使用的,
如果没有namespace,就剩下id,那么,id重复会导致数据互相覆盖。
有了namespace,自然id就可以重复,namespace不同,namespace+id自然也就不同。
但是,在以前的MyBatis版本的namespace是可选的,不过新版本的namespace已经是必须的了。
12、MyBatis的一级、二级缓存?
一级缓存: 基于 PerpetualCache 的 HashMap 本地缓存,其存储作用域为 Session,
当 Session flush 或 close 之后,该 Session 中的所有 Cache 就将清空,默认打开一级缓存。
二级缓存与一级缓存其机制相同,默认也是采用 PerpetualCache,HashMap 存储,
不同在于其存储作用域为 Mapper(Namespace),并且可自定义存储源,如 Ehcache。
默认不打开二级缓存,要开启二级缓存,使用二级缓存属性类需要实现Serializable序列化接口(可用来保存对象的状态),可在它的映射文件中配置
对于缓存数据更新机制,当某一个作用域(一级缓存 Session/二级缓存Namespaces)的进行了操作后,默认该作用域下所有 select 中的缓存将被 clear 掉并重新更新,
如果开启了二级缓存,则只根据配置判断是否刷新。
13、使用MyBatis的mapper接口调用时有哪些要求?
1:Mapper接口方法名和mapper.xml中定义的每个sql的id相同;
2:Mapper接口方法的输入参数类型和mapper.xml中定义的每个sql 的parameterType的类型相同;
3:Mapper接口方法的输出参数类型和mapper.xml中定义的每个sql的resultType的类型相同;
4:Mapper.xml文件中的namespace即是mapper接口的类路径。
14、MyBatis plus 了解过么?和MyBatis有啥区别?
MyBatis-Plus是一个MyBatis的增强工具,它在MyBatis的基础上只做增强,却不做改变。
我们在使用MyBatis-Plus之后既可以使用MyBatis-Plus的特有功能,又能够正常使用MyBatis的原生功能。
MyBatis-Plus(简称MP)是为简化开发、提高开发效率而生,自带通用mapper的单表操作。
15、MyBatis框架及原理?
MyBatis 是支持定制化 SQL、存储过程以及高级映射的优秀的持久层框架,其主要就完成2件事情:
- 封装JDBC操作
- 利用反射打通Java类与SQL语句之间的相互转换
MyBatis的主要设计目的就是让我们对执行SQL语句时对输入输出的数据管理更加方便,
所以方便地写出SQL和方便地获取SQL的执行结果才是MyBatis的核心竞争力;
执行流程包括
1.读取配置文件,配置文件包含数据库连接信息和Mapper映射文件或者Mapper包路径。
2.有了这些信息就能创建SqlSessionFactory,SqlSessionFactory的生命周期是程序级,程序运行的时候建立起来,程序结束的时候消亡
3.SqlSessionFactory建立SqlSession,目的执行sql语句,SqlSession是过程级,一个方法中建立,方法结束应该关闭
4.当用户使用mapper.xml文件中配置的的方法时,MyBatis首先会解析sql动态标签为对应数据库sql语句的形式,并将其封装进MapperStatement对象,然后通过executor将sql注入数据库执行,并返回结果。
5.将返回的结果通过映射,包装成java对象。
锁
地址:https://www.cnblogs.com/zwtblog/tag/并发/
1、介绍一下乐观锁和悲观锁
乐观锁的话就是比较乐观,每次去拿数据的时候,认为别人不会修改,所以不会上锁,
但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,
可以使用版本号机制或者CAS 算法实现。
乐观锁在读操作比较多的场景比较适用,这样可以提高吞吐量,就像数据库提供的write_condition机制,其实都是乐观锁
悲观锁的话就是每次去拿数据的时候,也认为别人会修改数据,这个时候就会加上锁,这就导致其他线程想拿数据的话,就会阻塞,直到这个线程修改完成才会释放锁,让其他线程获取数据。
在数据库里的行级锁、表级锁都是在操作之前就先锁住数据再操作数据 ,都属于悲观锁。
Java中的 synchronized 和 ReentrantLock 等独占锁就是悲观锁思想的实现
Java中各种锁其实都是悲观锁的实现,在操作数据的时候,数据都会被当前线程锁住。
2、介绍一下公平锁和非公平锁
公平锁:指线程在等待获取同一个锁的时候,是严格按照申请锁的时间顺序来进行的,这
就意味着在程序正常运行的时候,不会有线程执行不到的情况,
但是也需要额外的机制来维护这种顺序,所以效率相对于非公平锁会差点
非公平锁:概念跟“公平锁”恰恰相反,随机线程获取锁,效率相对高,可能会导致某些线程一直获取不到CPU资源而执行不到
创建一个ReentrantLock默认就是非公平锁,当然也可以传入参数让他变成公平锁
new ReentrantLock(); //默认非公平锁
new ReentrantLock(true); //公平锁
3、重入锁(递归锁)和不可重入锁(自旋锁)
//demo不用记忆,理解代码
public class Demo {
private Lock lockA;//这是个锁
public Demo(Lock Lock) {
this.lockA = lock;
}
public void methodA() {//业务逻辑A
lockA.lock();//获取锁,加锁
methodB();//执行业务逻辑B
lockA.unlock();//解锁
}
public void methodB() {//业务逻辑B
lockA.lock();//同样获取锁,加锁操作
//dosm
lockA.unlock();//解锁
}
}
重入锁:当我们运行methodA()的时候,线程获取了lockA,然后调用methodB()的时候发现也需要lockA,
由于这是一个可重入锁,所以当前线程也是可以直接进入的。
在java中,synchronized跟ReetrantLock都是可重入锁。
不可重入锁:methodA进入methodB的时候不能直接获取锁,必须先调用unLock释放锁。才能执行下去
4、共享锁和独占锁
java 并发包提供的加锁模式分为独占锁和共享锁。
独占锁
独占锁模式下,每次只能有一个线程能持有锁,ReentrantLock 就是以独占方式实现的互斥锁。
独占锁是一种悲观保守的加锁策略,它避免了线程之间读取数据发生冲突,如果某个只读线程获取锁,则其他读线程都只能等待
共享锁
共享锁则允许多个线程同时获取锁,并发访问 共享资源,比如说:ReadWriteLock。
共享锁则是一种乐观锁,它放宽了加锁策略,允许多个执行读操作的线程同时访问共享资源。
5、synchronized和ThreadLocal的区别
synchronized关键字主要解决多线程共享数据的同步问题。
ThreadLocal使用场合主要解决多线程中数据因并发产生不一致问题。
ThreadLocal和Synchonized都用于解决多线程并发访问,
但是ThreadLocal与synchronized有本质的区别:
synchronized是利用锁的机制,使变量或代码块在某一时该只能被一个线程访问。
而ThreadLocal是为每一个线程都提供了变量的副本,使得每个线程在某一时间访问到的并不是同一个对象,
这样就隔离了多个线程对数据的数据共享。
在使用完成后需要注意的是,需要把 Threadlocal 里的数据移除。
而Synchronized却正好相反,它用于在多个线程间通信时能够获得数据共享。
6、ConcurrentHashMap如何实现线程安全
他的内部采用了的"分段锁"策略,
ConcurrentHashMap的主干是个Segment数组。
Segment 通过继承ReentrantLock 来进行加锁,所以每次需要加锁的操作锁住的是一个 segment,
这样只要保证每个 Segment 是线程安全的,也就实现了全局的线程安全。
一个Segment就是一个子哈希表,Segment里维护了一个HashEntry数组,默认有16 个 Segment,
所以理论上,最多可以同时支持 16 个线程并发写,只要它们的操作分别分布在不同的 Segment 上。
MySQL
地址:https://www.cnblogs.com/zwtblog/tag/MySQL/
1、解释一下单列索引和联合索引
单列索引是指在表的某一列上创建索引,联合索引是在多个列上联合创建索引。
单列索引可以出现在where条件的任何位置,而联合索引需要按照一定的顺序来写。
在多条件查询的时候,联合索引的效率更高,我们联合索引也最多创建两列。
我们创建索引的时候也得考虑到我们这张表的更新频率,
如果表里索引比较多的话是比较影响更新速度的,因为创建索引的过程其实就是构建一个二叉树,
而每次更新完数据都得重新计算二叉树,所以就影响更新速度。
索引并不是时时都会生效的,比如以下几种情况就能导致索引失效:
-
如果条件中有or,即使其中有条件带索引也不会使用,
这也是为什么尽量少用or的原因,如果要想使用or,又想让索引生效,只能将or条件中的每个列都加上索引
-
like查询是以%开头,会导致索引失效
-
如果列类型是字符串,那一定要在条件中将数据使用引号引用起来,否则索引失效
-
如果mysql估计使用全表扫描要比使用索引快,则不使用索引
2、使用索引查询的优缺点
使用索引优点:
第一:可以保证数据库表中每一行的数据的唯一性,
第二:可以大大加快数据的索引速度,在使用分组和排序语句
进行数据检索时,同样可以显著减少查询中分组和排序的时间;
缺点:创建索引和维护索引要耗费时间,这种时间随着数据量的增加而增加
3、mysql存储引擎都有哪些,有什么区别
我了解到的数据库搜索引擎有MyISAM、InnoDB、BDB、MEMORY等,
对于 MySQL 5.5 及更高版本,默认的存储引擎是 InnoDB。
在 5.5 版本之前,MySQL 的默认存储引擎是 MyISAM
-
InnoDB 存储引擎:
-
支持自增长列(auto_increment),自增长列的值不能为空,
如果在使用的时候为空的话就会从现有的最大值自动+1,如果有但是比现在的还大,则就保存这个值。
-
支持外键(foreign key),外键所在的表称为子表而所依赖的表称为父表。
-
支持事务,回滚以及系统崩溃的修复能力,并且支持多版本并发控制的事务安全。
-
支持mvcc(多版本并发控制)的行级锁,就是通过多版本控制来实现的乐观锁
-
索引使用的是B+Tree
优点:InnoDB的优势在于提供了良好的事务处理、崩溃修复能力和并发控制。
缺点是读写效率较差,占用的数据空间相对较大。
-
-
MyISAM 存储引擎
不支持事务、支持表级锁
支持全文搜索
缓冲池只缓存索引文件,
不缓存数据文件 MyISAM 存储引擎表由 数据文件(MYD)和索引文件( MYI)组成
我们项目中常用到的是innoDB,InnoDB存储引擎提供了具有提交、回滚和崩溃恢复能力的事务安全,
但是对比Myisam的存储引擎InnoDB写的处理效率差一些并且会占用更多的磁盘空间以保留数据和索引。
4、创建索引的原则
- 经常需要搜索的列上建立索引,可以加快搜索的速度。
- 在作为主键的列上创建索引,强制该列的唯一性,并组织表中数据的排列结构。
- 在经常使用表连接的列上创建索引,这些列主要是一些外键,可以加快表连接的速 度。
- 在经常需要根据范围进行搜索的列上创建索引,因为索引已经排序,所以其指定的 范围是连续的。
- 在经常需要排序的列上创建索引,因为索引已经排序,所以查询时可以利用索引的 排序,加快排序查询。
- 在经常使用 WHERE 语句的列上创建索引,加快条件的判断速度。
5、如何查看查询语句索引是否生效
使用 explain 执行计划查看 在sql前面加入关键字explain 查询出的结果查看type类型检查是否有执行索引
举例:EXPLAIN select * from table where id=2
我们一般优化sql语句的话,type级别都要至少达到ref级别,就是每次查询必须要使用索引
- explain之后返回的列
列 | 说明 |
---|---|
id |
在一个大的查询语句中每个SELECT 关键字都对应一个唯一的id |
select_type |
SIMPLE(simple):简单SELECT(不使用UNION或子查询)。 PRIMARY(primary):子查询中最外层查询,查询中若包含任何复杂的子部分,最外层的select被标记为PRIMARY。 UNION(union):UNION中的第二个或后面的SELECT语句。 DEPENDENT UNION(dependent union):UNION中的第二个或后面的SELECT语句,取决于外面的查询。 UNION RESULT(union result):UNION的结果,union语句中第二个select开始后面所有select。 SUBQUERY(subquery):子查询中的第一个SELECT,结果不依赖于外部查询。 DEPENDENT SUBQUERY(dependent subquery):子查询中的第一个SELECT,依赖于外部查询。 DERIVED(derived):派生表的SELECT (FROM子句的子查询)。 UNCACHEABLE SUBQUERY(uncacheable subquery):(一个子查询的结果不能被缓存,必须重新评估外链接的第一行) |
table |
表名 |
partitions |
匹配的分区信息 |
type |
针对单表的访问方法,参考下面的说明 |
possible_keys |
可能用到的索引 |
key |
实际上使用的索引 |
key_len |
实际使用到的索引长度 |
ref |
当使用索引列等值查询时,与索引列进行等值匹配的对象信息 |
rows |
预估的需要读取的记录条数 |
filtered |
某个表经过搜索条件过滤后剩余记录条数的百分比 |
Extra |
一些额外的信息 |
- type说明
类型 | 说明 |
---|---|
All | 最坏的情况,全表扫描 |
index | 和全表扫描一样。只是扫描表的时候按照索引次序进行而不是行。主要优点就是避免了排序, 但是开销仍然非常大。如在Extra列看到Using index,说明正在使用覆盖索引,只扫描索引的数据,它比按索引次序全表扫描的开销要小很多 |
range | 范围扫描,一个有限制的索引扫描。key 列显示使用了哪个索引。当使用=、 <>、>、>=、<、<=、IS NULL、<=>、BETWEEN 或者 IN 操作符,用常量比较关键字列时,可以使用 range |
ref | 一种索引访问,它返回所有匹配某个单个值的行。此类索引访问只有当使用非唯一性索引或唯一性索引非唯一性前缀时才会发生。这个类型跟eq_ref不同的是,它用在关联操作只使用了索引的最左前缀,或者索引不是UNIQUE和PRIMARY KEY。ref可以用于使用=或<=>操作符的带索引的列。 |
eq_ref | 最多只返回一条符合条件的记录。使用唯一性索引或主键查找时会发生 (高效) |
const | 当确定最多只会有一行匹配的时候,MySQL优化器会在查询前读取它而且只读取一次,因此非常快。当主键放入where子句时,mysql把这个查询转为一个常量(高效) |
system | 这是const连接类型的一种特例,表仅有一行满足条件。 |
Null | 意味说mysql能在优化阶段分解查询语句,在执行阶段甚至用不到访问表或索引(高效) |
6、有没有做过数据库建模,自己是设计表和模块
数据库建模就是使用PowerDesigner工具,先分析项目需求,前端先出相应的原型,根据原型,
我开始做相应的表,设计初期的时候表会有些小浮动修改等,再根据需求设计详细字段。
如果后期客户需求改变时,表结构后期跟着调整,就是这样使用工具不断完善过程就是建模,
不过一些小的项目的话,简单的通过Navicat里的模型工具就可以实现了
7、左连接、右连接、内连接的区别
内连接的话,就是两表关联的数据才能查出来,关联不到的就查询不到。
左连接就是以左表为主,左表数据全查,
右表数据没有就显示null,右连接相反
举个例子吧,
比如员工和部门表,如果要查询出每个员工的信息以及他的部门信息,那么这个时候用内连接最合适。
如果要查询出每个部门下对应的员工信息,那么就需要以部门表为左表,进行左连接查询。
这样的话,没有员工的部门也可以被查询出来。
8、 count(1)和count(*) 有什么区别
从执行结果来看count(*)和count(1)没有区别,因为他们都不过滤空值
从执行效率来看MySQL会对count(*)做优化
(1)如果列为主键,count(列名)效率优于count(1)
(2)如果列不为主键,count(1)效率优于count(列名)
(3)如果表中存在主键,count(主键列名)效率最优
(4)如果表中只有一列,则count(*)效率最优
9、mysql查询语句的优化?
-
对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及 的列上建立索引。
-
应尽量避免在 where 子句中使用!=或<>操作符,否则引擎将放弃使用索引而进行 全表扫描。
-
应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用 索引而进行全表扫描,
如:
select id from t where num is null
可以在 num 上设置默认值 0,确保表中 num 列没有 null 值,然后这样查询:
select id from t where num=0
-
应尽量避免在 where 子句中使用 or 来连接条件,否则将导致引擎放弃使用索引 而进行全表扫描,
如:
select id from t where num=10 or num=20
,可以使用可以这样查询:
select id from t where num=10 union all select id from t where num=20
-
以%开头的模糊查询也会导致全表扫描:
select id from t where name like '%abc%'
,如果要提高效率的话,可以考虑全文检索来解决。
-
in 和 not in 也要慎用,否则会导致全表扫描,
如:
select id from t where num in(1,2,3)
对于连续的数值,能用 between 就不要用 in 了:
select id from t where num between 1 and 3
-
应尽量避免在 where 子句中对字段进行表达式操作,这将导致放弃使用索引 而进行全表扫描。
如:
select id from t where num/2=100
应改为:select id from t where num=100*2
-
应尽量避免在 where 子句中对字段进行函数操作,这将导致引擎放弃使用索引而 进行全表扫描。
比如说查询name以abc开头的数据:
select id from t where substring(name,1,3)='abc'
,可以改为
select id from t where name like 'abc%'
-
不要在 where 子句中的“=”左边进行函数、算术运算或其他表达式运算,否则系 统将可能无法正确使用索引。
-
在使用索引字段作为条件时,如果该索引是复合索引,
那么必须使用到该索引中 的第一个字段作为条件时才能保证系统使用该索引,否则该索引将不会被使用,并且应尽可 能的让字段顺序与索引顺序相一致。
-
很多时候用 exists 代替 in 是一个好的选择:
select num from a where num in(select num from b)
用下面的语句替换:select num from a where exists(select 1 from b where num=a.num)
-
并不是所有索引对查询都有效,SQL 是根据表中数据来进行查询优化的,
当索引 列有大量数据重复时,SQL 查询可能不会去利用索引,
如一表中有字段 sex,男、女的值 几乎各一半,那么即使在 sex 上建了索引也对查询效率起不了作用。
-
索引并不是越多越好,索引固然可以提高相应的 select 的效率,
但同时也降低 了 insert 及 update 的效率,因为 insert 或 update 时有可能会重建索引,
所以怎样建索 引需要慎重考虑,视具体情况而定。一个表的索引数最好不要超过 6 个
10、mysql批量插入5000条数据如何优化?
- 第一种方法:
合并sql插入语句,合并后日志量减少,降低日志刷盘的数据量和频率,从而提高效率,
通过合并SQL语句,同时也能减少SQL语句解析的次数,减少网络传输的IO
比如:INSERT INTO table(uid,content, type) VALUES ('userid_0', 'content_0', 0);
改为:
INSERT INTO table (uid,content, type) VALUES ('userid_0', 'content_0', 0), ('userid_1','content_1', 1);
- 第二种方法:
在同一个事务中进行插入处理
这是因为进行一个INSERT操作时,MySQL内部会建立一个事务,在事务内才进行真正插入处理操作。
通过使用同一个事务可以减少创建事务所消耗的时间,所有插入都在执行后才统一进行提交操作。
11、mysql查询重复数据?
比如A表有字段id,pid,sname,
查询重复数据:
select * from A
where pid in (select pid from A group by pid having count(pid) > 1);
12、了解过MySQL存储过程和视图吗,介绍一下
- 存储过程
存储程序是被存储在服务器中的组合SQL语句,
经过创建编译并保存在数据库中,用户可通过存储过程的名字调用执行。
存储过程核心思想就是数据库SQL语言层面的封装与复用。
使用存储过程可以较少应对系统的业务复杂性,但是会增加数据库服务器系统的负荷,所以在使用的时候需要综合业务考虑。
对应存储过程的名字使用call调用 ,把对应的参数传递进去,输出参数使用@声明
-- 创建存储过程
DROP PROCEDURE IF EXISTS p01_discount; //如果存在先删掉再创建
CREATE PROCEDURE p01_discount(IN consume NUMERIC(5,2),OUT payfee NUMERIC(5,2)) //声明存储过程,in输入参数 out输出参数
BEGIN
--判断收费方式
IF(consume>100.00AND consume<=300.00) THEN
SET payfee=consume*0.8;
ELSEIF(consume>300.00) THEN
SET payfee=consume*0.6;
ELSE
SET payfee = consume;
END IF;
SELECT payfee AS result;
END ;
-- 调用存储过程
CALL p01_discount(100.0,@discount);
- 视图
视图本身是一张虚拟表,不存放任何数据。
在使用SQL语句访问视图的时候,获取的数据是MySQL从其它表中生成的,视图和表在同一个命名空间(因为表和视图共享数据库中相同的名称空间,
因此,数据库不能包含具有相同名称的表和视图)。
视图查询数据相对安全,视图可以隐藏一些数据和结构,只让用户看见权限内的数据,使复杂的查询易于理解和使用。
比如:用的是5张表的联查,然后用sql语句来写的话,比较慢,比较麻烦,
然后我们把这5张表的联查创建了一个视图,然后就直接查找的是视图,查询速度快,这个视图就是只能做查询,而不能做增删改操作
-- 创建视图
CREATE OR REPLACE VIEW user_order_view AS
SELECT
t1.id,t1.user_name,t2.order_no,t2.good_id,
t2.good_name,t2.num,t2.total_price
FROM v01_user t1
LEFT JOIN v02_order t2 ON t2.user_id =t1.id;
-- 视图调用
SELECT * FROM user_order_view WHERE user_name='Cicada';
13、where和having的区别
这两个都是添加查询条件用的。
where的话就是拼接普通字段的查询条件,having后边跟上聚合之后数据的查询条件。
比如计算平均薪资在10k以上的部门信息,这会儿的话就要用select xx from table group by deptId having avg(salary)>10000
常用的聚合函数有:count、sum、avg、min、max
14、数据库三范式
第一范式,原子性,列或者字段不能再分
第二范式的话要满足第一范式,并且不可以把多种数据保存在同一张表中,即一张表只能保存一类数据,否则可能导致插入数据异常。
第三范式,直接性,不存在传递依赖,他要在满足第二范式的条件上,在每一列都和主键直接相关,而不能间接相关。
15、select语句的执行顺序
from--->where--->group by--->having--->计算所有的表达式--->order by-- ->select 输出
大致上是这么个顺序,如果sql里有子查询的话,也会按照这个方式来执行的
16、mysql分库分表
分库分表的话,是解决MySQL数据量多了之后,单表单库存储量多了之后查询效率低下等问题的,
主要分为两种方式,一个是水平拆分,另一个是垂直拆分
垂直拆分的话就是
单个表中比如30个字段,拆分为两个表,一个表20个字段,一个表10个字段这样,
或者按照其他方式拆分成3个表,
这样的拆分原则呢就是将大字段或者不经常修改的或者经常查询的字段拆分出来,作为单独的表存储,
然后跟主表一对一的关系存储,这样的话水平扩展了表,并且对功能也做了分离,
高并发场景下,垂直拆分一定程度的提升IO性能,不过依然存在单表数据量过大的问题
水平拆分的话就是按照数据量来拆分
比如我们的表里,每个表最多存储200W条数据,然后每个表命名方式为user_0001、user_0002的方式,
在查询的时候,用逻辑代码来控制数据查询。
这样的话不存在单表单库数据量过大、高并发的性能瓶颈,提升系统稳定性和负载能力。
不过水平拆分会导致跨分片的事务一致性难以保证,跨库的join关联查询性能较差,要根据具体的业务来判断具体适用那种分表方式
我们都是结合读写分离和MySQL集群做的,
读写分离以及集群的话,读写分离话保证了数据的安全性,集群的话其实就相当于水平拆分。
这个我们项目中使用MyCat来做的,在MyCat里配置好主库和从库,做增删改的时候是对主库进行操作,
查询的时候是对从库进行操作,其实mysql本身从5.6以后的版本就带主从复制的功能了,他们是利用MySQL里的log文件进行的数据同步
JVM
地址:https://www.cnblogs.com/zwtblog/tag/JVM/
1、介绍下JVM
类加载器:加载类文件到内存。
Class loader只管加载,只要符合文件结构就加载,至于能否运行,它不负责,那是由执行引擎负责的
执行引擎:负责解释命令,交由操作系统执行
本地接口:本地接口的作用是融合不同的语言为java所用。
JVM的运行时数据区分为五个区域:堆、虚拟机栈、本地方法栈、方法区、程序计数器。
其中虚拟机栈、本地方法栈、程序计数器为线程私有,方法区和堆为线程共享区,JVM不同区域的占用内存大小不同,一般情况下堆内存最大,程序计数器较小。
程序计数器:这里记录了线程执行的字节码的行号,在分支、循环、跳转、异常、线程恢复等都依赖这个计数器。
如果线程正在执行的是一个java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;
如果正在执行的是Native方法,这个计数器值为空
java虚拟机栈:每个方法执行的时候都会创建一个栈帧,用于存放 局部变量表、操作栈、动态链接、方法出口。
每一个方法从调用直到执行完成的过程,就对应一个栈帧在虚拟机中入栈到出栈的过程。
本地方法栈:与虚拟机栈很类似,区别是一个是执行Java方法,一个是执行本地方法。有的虚拟机会把这2个栈合二为一。
堆:Java堆是Java虚拟机所管理的内存最大的一块,被所有线程共享的一块内存区域,在虚拟机启动的时候就创建了。
这个内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。
Java堆是垃圾收集器(GC)管理的主要区域,有时候也被称为“GC堆”。
因为现在收集器基本都采用分代收集算法,所有Java堆还可以细分为:新生代和老年代。
堆是可以固定大小也是可以扩展的,如果在堆内存不足,并且也无法及时扩展时,会抛出OutOfMemoryError异常。
方法区:用于存储已被Java虚拟机加载的类信息、常量、静态变量、以及编译器编译后的代码等数据。
2、介绍下内存泄漏和内存溢出
1、内存泄漏叫memory leak
:是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄漏似乎不会有大的影响,但内存泄漏堆积后的后果就是内存溢出。
2、内存溢出叫 out of memory
:指程序申请内存时,没有足够的内存给申请者使用,或者说,给了你一块存储int类型数据的存储空间,但是你却存储long类型的数据,那么结果就是内存不够用,此时就会报错OOM,就是所谓的内存溢出。
Java内存泄漏的根本原因是,长生命周期的引用指向了短生命周期的对象,导致内存无法被回收,
我给您说几个具体的场景吧
- 静态集合类造成的内存泄漏
static List list = new ArrayList(10);
public void method(){
for (int i = 1; i<100; i++){
Object o = new Object();
list.add(o);
o = null;
}
}
循环申请Object 对象,并将所申请的对象放入一个ArrayList 中,如果仅仅释放引用本身(o=null),
那ArrayList 仍然引用该对象,所以这个对象对GC 来说是不可回收的,就会导致内存泄漏。
因此,如果对象加入到ArrayList 后,还必须从ArrayList 中删除,最简单的方法就是将ArrayList对象设置为null
- 拦截器中导致内存泄漏
在很多拦截器中,比如总是会是使用Threadlocal存储一些线程变量,如果在方法请求完成时,没有将Threadlocal中的变量释放,那么也会导致内存泄漏
- 各种连接导致的内存泄漏
比如数据库连接,网络连接(socket)和io连接,除非显式的调用了其close()方法将其连接关闭,否则是不会自动被GC 回收的
3、列举一些会导致内存溢出的类型都有哪些,分别怎么造成的
- 第一种
OutOfMemoryError: PermGen space
发生这种问题的原意是程序中使用了大量的jar或class,使java虚拟机装载类的空间不够,与Permanent Generation space有关。解决这类问题有以下两种办法:
1、增加java虚拟机中的PermSize和MaxPermSize参数的大小,其中PermSize是初始永久保存区域大小,
MaxPermSize是最大永久保存区域大小。
比如说针对tomcat,在catalina.sh
文件中增加这两个参数的配置就行了(一系列环境变量名说明结束处(大约在70行左右)
增加一行:JAVA_OPTS=" -XX:PermSize=64M -XX:MaxPermSize=128m"
)。
如果是windows服务器还可以在系统环境变量中设置。
用tomcat发布ssh架构的程序时很容易发生这种内存溢出错误。
使用上述方法,我成功解决了部署ssh项目的tomcat服务器经常宕机的问题。
2、清理应用程序中web-inf/lib
下的jar,如果tomcat部署了多个应用,很多应用都使用了相同的jar,
可以将共同的jar移到 tomcat共同的lib下,减少类的重复加载。
- 第二种
OutOfMemoryError: Java heap space
发生这种问题的原因是java虚拟机创建的对象太多,
在进行垃圾回收之前,虚拟机分配的到堆内存空间已经用满了,与堆空间大小有关。解决这类问题有两种思路:
1、检查程序,看是否有死循环或不必要地重复创建大量对象。
找到原因后,修改程序和算法。
2、增加Java虚拟机中Xms(初始堆大小)和Xmx(最大堆大小)参数的值。(如:set JAVA_OPTS= -Xms256m -Xmx1024m)
- 第三种
OutOfMemoryError:unable to create new native thread
在Java应用中,有时候会出现这样的错误。
这种错误是因为给JVM分配了过多内存导致的,比如超过可用内存的一半,就会导致这种问题。
在线程个数很多的情况下, 你分配给JVM的内存越多,那么,这种错误发生的可能性就越大。
比如说系统可用内存一共2G,这里假设分配1.5G给JVM,那么还余下500M可用内存。
这 500M内存中的一部分必须用于系统文件加载,那么真正剩下的也许只有400M,
但是关键是,当你使用Java创建一个线程,
在JVM的内 存里也会创建一个Thread对象,但是同时也会在操作系统里创建一个真正的物理线程,
操作系统会在剩下的400兆内存里创建这个物理 线程,而不是在JVM的1.5G的内存堆里创建。
在jdk1.4里头,默认的栈大小是256KB,但是在jdk1.5之后,默认的栈大小为1M每个线程,
因此,在余下400M的可用内存里边我们最多也只能创建400个可用线程。
这种情况的话,要想创建更多的线程,你必须减少分配给JVM的最大内存,或者增加系统的内存
4、JVM中垃圾回收的算法
- 引用计数器算法
对象中添加一个引用计数器,如果引用计数器为0则表示没有其它地方在引用它。如果有一个地方引用就+1,引用失效时就-1。
看似搞笑且简单的一个算法,实际上在大部分Java虚拟机中并没有采用这种算法,因为它会带来一个致命的问题——对象循环引用。
对象A指向B,对象B反过来指向A,此时它们的引用计数器都不为0,但它们俩实际上已经没有意义因为没有任何地方指向它们。
- 可达性分析算法
这种算法可以有效地避免对象循环引用的情况,整个对象实例以一个树的形式呈现,根节点是一个称为“GC Roots”的对象,
从这个对象开始向下搜索并作标记,遍历完这棵树过后,未被标记的对象就会判断“已死”,即为可被回收的对象。
- 标记-清除算法
这个方法是将垃圾回收分成了两个阶段:标记阶段和清除阶段。
在标记阶段,通过根对象,标记所有从跟节点可达的对象,那么未标记的对象就是未被引用的垃圾对象。
在清除阶段,清除掉所以的未被标记的对象。
这个方法的缺点是,垃圾回收后可能存在大量的磁盘碎片,准确的说是内存碎片。
- 标记-整理算法
在标记清除算法的基础上做了一个改进,可以说这个算法分为三个阶段:标记阶段,压缩阶段,清除阶段。
标记阶段和清除阶段不变,只不过增加了一个压缩阶段,就是在做完标记阶段后,将这些未被标记过的对象集中放到一起,
确定开始和结束地址,比如全部放到开始处,这样再去清除,将不会产生磁盘碎片。
但是我们也要注意到几个问题,压缩阶段占用了系统的消耗,并且如果未标记对象过多的话,损耗可能会很大,
在未标记对象相对较少的时候,效率较高。
- 复制算法(Java中新生代采用)
核心思想是将内存空间分成两块,同一时刻只使用其中的一块,在垃圾回收时将正在使用的内存中的存活的对象复制到未使用的内存中,
然后清除正在使用的内存块中所有的对象,然后把未使用的内存块变成正在使用的内存块,
把原来使用的内存块变成未使用的内存块。
很明显如果存活对象较多的话,算法效率会比较差,并且这样会使内存的空间折半,但是这种方法也不会产生内存碎片。
此GC算法实际上解决了标记-清除算法带来的“内存碎片化”问题。
首先还是先标记出待回收内存和不用回收的内存,下一步将不用回收的内存复制到新的内存区域,这样旧的内存区域就可以全部回收,而新的内存区域则是连续的。
它的缺点就是会损失掉部分系统内存,因为你总要腾出一部分内存用于复制。
- 分代搜集算法(Java堆采用)
主要思想是根据对象的生命周期长短特点将其进行分块,
根据每块内存区间的特点,使用不同的回收算法,从而提高垃圾回收的效率。
Java 中的堆是 JVM 所管理的最大的一块内存空间,主要用于存放各种类的实例对象。
在 Java 中,堆被划分成两个不同的区域:新生代 ( Young )、老年代 ( Old )。新生代 ( Young )
又被划分为三个区域:Eden、From Survivor、To Survivor。
这样划分的目的是为了使 JVM 能够更好的管理堆内存中的对象,包括内存的分配以及回收。
JVM 每次只会使用 Eden 和其中的一块 Survivor 区域来为对象服务,
所以无论什么时候,总是有一块 Survivor 区域是空闲着的。
因此,新生代实际可用的内存空间为 90% 的新生代空间。
新生代垃圾回收采用复制算法,清理的频率比较高。如果新生代在若干次清理中依然存活,则移入老年代,
有的内存占用比较大的直接进入老年代。
老年代使用标记清理算法,清理的频率比较低。
- 分区算法
这种方法将整个空间划分成连续的不同的小区间,每个区间都独立使用,独立回收,
好处是可以控制一次回收多少个小区间。
5、类加载的过程
- 加载:根据查找路径找到相应的 class 文件然后导入;
- 检查:检查加载的 class 文件的正确性;
- 准备:给类中的静态变量分配内存空间;
- 解析:虚拟机将常量池中的符号引用替换成直接引用的过程。符号引用就理解为一个标示,而直接引用是直接指向内存中的地址;
- 初始化:对静态变量和静态代码块执行初始化工作。
说到这儿我再说下new一个对象之后,类实例化的顺序吧
- 首先是父类的静态变量和静态代码块(看两者的书写顺序);
- 第二执行子类的静态变量和静态代码块(看两者的书写顺序);
- 第三执行父类的成员变量赋值
- 第四执行父类的普通代码块
- 第五执行父类的构造方法()
- 第六执行子类的普通代码块
- 第七执行子类的构造方法();
也就是说虽然客户端代码是new 的构造方法,但是构造方法确实是在整个实例创建中的最后一个调用
6、怎么判断对象是否可以被回收
一般有两种方法来判断:
-
引用计数器:为每个对象创建一个引用计数,
有对象引用时计数器 +1,引用被释放时计数 -1,当计数器为 0 时就可以被回收。
它有一个缺点不能解决循环引用的问题;
-
可达性分析:从 GC Roots 开始向下搜索,搜索所走过的路径称为引用链。
当一个对象到 GC Roots 没有任何引用链相连时,则证明此对象是可以被回收的。
7、说一下JVM调优的工具
JDK 自带了很多监控工具,都位于 JDK 的 bin 目录下,其中最常用的是 jconsole 和 jvisualvm 这两款视图监控工具。
- jconsole:用于对 JVM 中的内存、线程和类等进行监控;
- jvisualvm:JDK 自带的全能分析工具,可以分析:内存快照、线程快照、程序死锁、监控内存的变化、gc 变化等。
- jstat:jstat 命令可以查看堆内存各部分的使用量,以及加载类的数量
- jmap:是用于查看指定Java进程的堆内存使用情况
8、详细介绍下JVM堆中的内存模型
我先给您介绍下jdk1.7中的堆的情况吧
- Young 年轻代
年轻代区域被划分为三部分,Eden区和两个大小严格相同的Survivor区,
其中,Survivor区间中,某一时刻只有其中一个是被使用的,另外一个留做垃圾收集时复制对象用,
在Eden区间变满的时候,GC就会将存活的对象移到空闲的Survivor区间中,
根据JVM的策略,在经过15次垃圾收集后,仍然存活于Survivor的对象被移动到老年代。
- Tenured 年老代
老年带主要保存生命周期长的对象,一般是一些老的对象,
当一些对象在年轻代复制转移一定的次数以后,对象就会被转移到老年代,
一般如果系统中用了application级别的缓存,缓存中的对象往往会被转移到这一区间
- Perm 永久代
永久代主要保存 class、method、filed对象,这部分的空间如果使用不当,就会造成内存溢出,
比如一次性加载了很多的类,或者一个tomcat下部署了几十个应用,
不过在涉及到热部署的服务器的时候,有时候会遇到 java.lang.OutOfMemoryError:PermGenSpace的错误,造成这个错误的很大原因就有可能是每次都重新部署,
但是重新部署后,类的class没有被卸载掉,这样就造成了大量的class对象保存在永久代中,
不过这种情况下,一般重新启动服务器可以解决问题
- Virtual虚拟区
最大内存和初始内存的差值,就是虚拟区
而在jdk1.8中,最大的变化就是用元数据空间替代了永久代,
这块所占用的内存空间不是在虚拟机内部的,而是在本地内存空间中,
我看了下官网的解释,是因为了后续要融合两个JVM的版本,
因为一个版本中没有设计永久代这个概念,另外一方面就是在我们现实使用中,
由于永久代内存经常不够用或发生内存泄露,因此将永久代废弃,而改用元数据空间,改为了使用本地内存空间
Linux
https://www.cnblogs.com/zwtblog/p/15174106.html
1、linux常用命令
命令 | 解释 | 备注 |
---|---|---|
rm -rf | 删除服务器所有的文件 | -r 代表递归删除;-f代表强制删除;rm -rf /* |
mkdir 文件夹名称 | 创建文件夹 | mkdir -p a/b/c ;可以创建多级目录 |
pwd | 显示当前绝对路径 | |
tail -f xxx.log | 监控并输出最后几行内容 | 一般用于查看日志 |
echo | 输出内容到控制台,或者文本 | echo "xxx" > a.txt 覆盖原有文本; echo "xxx" >> a.txt 追加到原有文本最后一行 touch xxx.txt 创建文件 |
ps -ef / ps aux | 查看系统进程 | 一般跟grep结合使用,查找某个进程用; 例如:`ps -ef |
grep | 查找内容 | 可以查找文件中的内容 |
yum install -y xxx | 使用yum安装应用 | -y 代表要输入yes或者no的时候,自动输入yes |
curl | 控制台访问某个链接 | curl ifconfig.me 可以查看本机公网ip |
wget | 一般用于下载文件 | |
uname -r | 显示正在使用的内核版本 | |
top | 实时监控系统使用情况 | 包括线程、内存、cpu等详细信息,curl+c退出监控 |
find 路径 -name 关键字 | 查找路径下的某些文件 | 例如:find / -name a.log 从根目录开始查找叫a.log的文件 关键字可以用*作为通配符, 例如:find / -name *.log 查找以log即为的文件 |
chmod | 给文件或者文件夹授权 | chmod -R 777 文件夹或者文件 |
df -h | 查看磁盘分区列表以及使用情况 | |
du -sh 文件夹或者文件 | 查看文件或者文件夹占用的磁盘空间 | |
cat 文件 | 输出文件内容到控制台 | 如果是大文件,会导致控制台输出过多,可以使用more或者less替代 more是从前往后一页一页的输入文件内容 按空格下一页,b上一页,q退出, more -n 文件 从第n行开始查看;使用more查看文件时,会先加载整个文件,再按照条件显示 less也是一页一页查看文件内容个,less不会一次性加载整个文件内容,查看多少,加载多少 |
nohup | 后台运行进程 | 例如后台运行springboot的jar项目:nouhp java -jar xxx.jar > /dev/null 2>&1 & |
2、服务器CPU一直100%,如何定位问题谈谈你能想到的思路
1、通过top -c 命令找出当前的进程列表,
接着按P(大写)可以按照CPU使用率排序,然后找到进程对应使用率高的进程ID
2、然后通过top -Hp 进程PID列举出当前进程的所有线程,
按P(大写)排序,找到对应线程的PID,转为16进制备用
3、然后通过jstack -l 线程PID > 文件,导出线程快照到这个文件里
4、然后通过grep命令查找文件中这个16进制的线程PID的行为进行分析
我这里只是一个自己的学习笔记,大家有兴趣一定去看原文!!! 谢谢大家的阅读!!
大家有兴趣一定去看原文,这只是我自己的一个笔记总结!!
大家有兴趣一定去看原文,这只是我自己的一个笔记总结!!
大家有兴趣一定去看原文,这只是我自己的一个笔记总结!!