一些面试题

题目1:SpringCloud组件有哪些,怎样应用的?

  1. 问题分析

 

考官主要想考察你对SpringCloud了解情况,尽量把你知道的SpringCloud组件相关说出来,最了解的组件最先说。不仅仅说出组件,要将组件的详细情况也一并介绍。

 

  1. 核心答案讲解

 

SpringCloud官方的架构图

  1. 注册中心:所谓注册中心,其本质是微服务的管理平台,包含服务端和客户端组件。在SpringCloud的生态圈中,注册中心组件主要有3个,最重量级的还是Eureka ,除此之外还有Consul,Zookeeper。最近1年阿里将自己的注册中心也提交给了SpringCloud,阿里的注册中心Nacos现在也可以整合到SpringCloud中。
  2. 负载均衡器SpringCloud中的负载均衡器叫Ribbon,它是一个工具组件,它是一个实现了Http和TCP客户端负载均衡的工具。负载均衡器算是分布式系统的基础设施。SpringCloud的负载均衡器运用非常广泛,在各个系统中均可见到。常见的负载均衡算法主要有:轮询算法RoundRobinRole、随机算法RandomRule、并发情况算法AvailabilityFilteringRule、响应时间算法WeightedResponseTimeRule
  3. 熔断器典型代表Hystrix,它是分布式系统中的一种自我保护机制,目的是阻止服务级联出错,防止雪崩效应。提升分布式系统的整体弹性。它也是分布式系统中的基础设施之一,与Ribbon并列。
  4. 远程过程调用典型代表Feign,它是一个Http请求调用的轻量级组件。作用类似于RestTemplate。它封装了Http调用的流程,使之更加符合面向接口化编程习惯。
  5. API网关典型代表是Gateway、Zuul。早期在用Zuul,现在基本使用Gateway。网关是分布式系统的API接口的统一入口,是我们服务系统的守门神。其本身也是一个服务,需要注册到注册中心。
  6. 配置中心典型代表Config,它是统一管理了所有服务配置文件的一个微服务,本身也是服务,需要注册到注册中心去。
  7. 消息总线典型代表Bus,主要用于做服务监控,可以实现应用程序直接的通讯,还可以用于配置文件的广播。注意Bus本身不支持消息缓存,需要借助第三方消息中间件MQ。来完成消息的收发
  8. SpringCloudAlibaba:阿里整套分布式系统组件,刚刚兴起
    1. 流量防卫兵Sentinel,使用它进行流量控制,熔断和系统自我保护
    2. 注册中心 Nacos也可以用于管理分布式系统中的服务,内部支持负载均衡器Ribbon
    3. 分布式配置中心也用的Nacos,其本身既是配置中心也是注册中心
    4. 消息总线SpringCloudBus RocketMQ,连接分布式系统的服务
    5. 分布式事务分布式事务解决方案Seata,性能很好
    6. 远程过程调用Dubbo RPC,高性能RPC框架

 

  1. 问题扩展

 

SpringCloud是分布式系统整套一站式解决方案,Spring Cloud从技术架构层面,降低了对大型系统构建的要求和难度,使我们以非常低的成本(技术或者硬件)搭建一套高效分布式容错平台,但Spring Cloud也不是没有缺点,小型独立的项目不适合使用。

SpringCloud没有重复造轮子,它自身定位就像胶水一样,目的是将各家比较成熟的技术融入到自己的生态系统中

 

  1. 结合项目中使用

 

快速搭建分布式系统基础设施的必备十八般武器,超酷的工具组合。

注册中心:管理分布式系统的服务注册发现

配置中心:管理集群的配置文件

熔断器:处理系统间的异常

负载均衡器:服务集群访问

API网关:对请求进行路由转发,请求地址鉴权

题目2:  Redis的使用以及哨兵机制

  1. 问题分析

考官主要针对redis”高可用”和”高并发”的考核. 我们可以从基本使用开始, 到如何保证高可用和高并发, 尽量把你知道的说出来, 最了解先说.

 

  1. 核心答案讲解

 

  1. Redis的基本使用

假如用户第一次访问数据库中的某些数据。这个过程会比较慢,因为是从硬盘上读取的。将该用户访问的数据存在缓存中,这样下一次再访问这些数据的时候就可以直接从缓存中获取了。操作缓存就是直接操作内存,所以速度相当快。如果数据库中的对应数据改变的之后,同步改变缓存中相应的数据即可!

直接操作缓存能够承受的请求是远远大于直接访问数据库的,所以我们可以考虑把数据库中的部分数据转移到缓存中去,这样用户的一部分请求会直接到缓存这里而不用经过数据库。

 

  1. Sentinel哨兵机制的工作原理总结
  1. 每个Sentinel以每秒钟一次的频率向它所知的Master,Slave以及其他 Sentinel 实例发送一个 PING 命令。
  2. 如果一个实例(instance)距离最后一次有效回复 PING 命令的时间超过 down-after-milliseconds 选项所指定的值, 则这个实例会被 Sentinel 标记为主观下线。 
  3. 如果一个Master被标记为主观下线,则正在监视这个Master的所有 Sentinel 要以每秒一次的频率确认Master的确进入了主观下线状态。 
  4. 当有足够数量的 Sentinel(大于等于配置文件指定的值)在指定的时间范围内确认Master的确进入了主观下线状态, 则Master会被标记为客观下线 。
  5. 在一般情况下, 每个 Sentinel 会以每 10 秒一次的频率向它已知的所有Master,Slave发送 INFO 命令 。
  6. 当Master被 Sentinel 标记为客观下线时,Sentinel 向下线的 Master 的所有 Slave 发送 INFO 命令的频率会从 10 秒一次改为每秒一次 。
  7. 若没有足够数量的 Sentinel 同意 Master 已经下线, Master 的客观下线状态就会被移除。 
  8. 若 Master 重新向 Sentinel 的 PING 命令返回有效回复, Master 的主观下线状态就会被移除。
  1. 哨兵机制主要功能如下?
  1. 集群监控,Sentinel 会不断地定期检查你的主服务器和从服务器是否运作正常。
  2. 消息通知,当被监控的某个 Redis 服务器出现问题时, Sentinel 可以通过 API 向管理员或者其他应用程序发送通知。
  3. 故障转移,如果master node挂掉了,会自动转移到slave node上
  4. 配置中心,如果故障转移发生了,通知client客户端新的master地址

 

  1. 问题扩展

 

  1. Redis如何防止数据丢失?

通过redis持久化将内存中的数据写入到硬盘中, 在redis重新启动的时候加载这些数据, 从而最大限度的降低缓存丢失带来的影响.

  1. 如何防止redis服务器宕机?

通过redis集群解决单点故障. redis最开始使用主从模式做集群. 若master宕机需要手动配置slave转为master;

后来为了高可用提出来哨兵模式, 该模式下有一个或多个哨兵监视master和slave, 若master宕机可以自动将slave转为master.

  1. Redis如何保证高并发, 高可用?

高并发:redis的单机吞吐量可以达到几万不是问题,如果想提高redis的读写能力,可以用redis的主从架构,redis天然支持一主多从的准备模式,单主负责写请求多从负责读请求,主从之间异步复制,把主的数据同步到从。

高可用:首先利用redis的主从架构解决redis的单点故障导致的不可用,然后如果使用的是主从架构,那么只需要增加哨兵机制即可,就可以实现,redis主实例宕机,自动会进行主备切换。以此来达到redis的高可用。`

  1. 缓存穿透概念

在高并发下,查询一个不存在的值时,缓存不会被命中,导致大量请求直接落到数据库上, 造成数据库的压力。

  1. 缓存雪崩概念

在高并发下,大量的缓存key在同一时间失效,导致大量的请求落到数据库上, 对于数据库而言, 就会产生周期性的压力波峰.

  1. 常见的缓存问题解决方案
  1. 直接缓存NULL值
  2. 限流
  3. 缓存预热
  4. 分级缓存
  5. 缓存永远不过期

 

  1. 结合项目中使用
  1. 热点数据的缓存
  2. 限时业务的运用
  3. 计数器相关问题
  4. 排行榜相关问题
  5. 分布式锁
  6. 延时操作
  7. 分页和模糊查询
  8. 队列

 

题目3:分表如何实现

(1)问题分析

在日常的工作中,关系型数据库本身比较容易成为系统的瓶颈点,虽然读写分离能分散数据库的读写压力,但并没有分散存储压力,当数据量达到千万甚至上亿时,单台数据库服务器的存储能力会成为系统的瓶颈,主要体现在以下几个方面:

  1. 数据量太大,读写的性能会下降,即使有索引,索引也会变得很大,性能同样会降下。
  2. 数据库文件会得很大,数据库备份和恢复需要耗时很长。
  3. 数据库文件越大,极端情况下丢失数据的风险越高。 因此,当流量越来越大时,且单机容量达到上限时,此时需要考虑对其进行切分,切分的目的就在于减少单机数据库的负担,将由多台数据库服务器一起来分担,缩短查询时间。

(2)核心答案讲解

分库分表具体可以划分为纵向切分和垂直切分两种

1. 纵向切分

  常见有纵向分库纵向分表两种。 1). 纵向分库就是根据业务耦合性,将关联度低的不同表存储在不同的数据库,做法与大系统拆分为多个小系统类似,按业务分类进行独立划分。与“微服务治理”的做法相似,每个微服务使用单独的一个数据库。

2). 垂直分表是基于数据库中的列进行,某个表字段较多,可以新建一张扩展表,将不经常用或者字段长度较大的字段拆出到扩展表中。在字段很多的情况下,通过大表拆小表,更便于开发与维护,也能避免跨页问题,MYSQL底层是通过数据页存储的,一条记录占用空间过大会导致跨页,造成额外的开销。另外,数据库以行为单位将数据加载到内存中,这样表中字段长度越短且访问频次较高,内存能加载更多的数据,命中率更高,减少磁盘IO,从而提升数据库的性能。

  1. 垂直切分的优点:
    1. 解决业务系统层面的耦合,业务清晰
    2. 与微服务的治理类似,也能对不同业务的数据进行分级管理,维护,监控,扩展等。
    3. 高并发场景下,垂直切分一定程度的提升IO,数据库连接数,单机硬件资源的瓶颈。
    4. 垂直切分的缺点
  2. 部分表无法join,只能通过接口聚合方式解决,提升了开发的复杂度。
    1. 分布式事务处理复杂
    2. 依然存在单表数据量过大的问题。

2. 水平切分

  当一个应用难以再细粒度的垂直切分或切分后数据量行数依然巨大,存在单库读写,存储性能瓶颈,这时候需要进行水平切分。   水平切分为库内分表和分库分表,是根据表内数据内在的逻辑关系,将同一个表按不同的条件分散到多个数据库或多表中,每个表中只包含一部分数据,从而使得单个表的数据量变小,达到分布式的效果。   库内分表只解决单一表数据量过大的问题,但没有将表分布到不同机器的库上,因些对于减轻mysql的压力来说帮助不是很大,大家还是竞争同一个物理机的CPU、内存、网络IO,最好通过分库分表来解决。

  1. 水平切分优点
    1. 不存在单库数据量过大、高并发的性能瓶颈,提升系统稳定性和负载能力。
    2. 应用端改造较小,不需要拆分业务模块。
    3. 水平切分缺点
  2. 跨分片的事务一致性难以保证
  3. 跨库的join关联查询性能较差
  4. 数据多次扩展维度和维护量极大。

(3)问题扩展

分库分表可以使用阿里的MyCat中间件比较方便容易实现, 具体分库分表路由方案如下:

水平切分后同一张表会出现在多个数据库或表中,每个库和表的内容不同,对于水平分表后分库后,如何知道哪条数据在哪个库里或表里,则需要路由算法进行计算,这个算法会引入一定的复杂性。

  1. 范围路由

  选取有序的数据列,如时间戳作为路由的条件,不同分段分散到不同的数据库表中,以最常见的用户ID为例,路由算法可以按照1000000的范围大小进行分段,1 ~ 9999999放到数据库1的表中,10000000~199999999放到数据库2的表中,以此累推。   范围路由设计的复杂点主要体现在分段大小的选取上,分段太小会导致切分后子表数量过多增加维护复杂度,分段太大可能会导致单表依然存在性能问题,按一般大老们的经验,分段大小100W至2000W之间,具体需要根据业务选 取合适的分段大小。

    1. 范围路由的优点
      1. 可以随着数据的增加平滑地扩充新的表或库,原有的数据不需要动。
      2. 单表大小可控
      3. 使用分片字段进行范围查找时,连续分片可快速定位查询,有效避免分片查询的问题。
    2. 热点数据成为性能瓶颈,连续分片可能存在数据热点,例如按时单字段分片,有些分片存储最近时间内的数据,可能会被频繁读写,而有些历史数据则很少被查询。
  1. hash算法

  选取某个列或几个列的值进行hash运算,然后根据hash的结果分散到不同的数据库表中,以用ID为例,假如我们一开始就规划10个数据库表,路由算法可以简单地用id % 10的值来表示数据所属的数据库编号,ID为985的用户放到编号为5的子表中。ID为10086编号放到编号为6的表中。   Hash路由设计的复杂点主要体现 在初始表数量的选取上,表数量太多维护比较麻烦,表数量太小又可能导致单表性能存在问题。而用Hash路由后,增加字表数量是非常麻烦的,所有数据都要重新分布。   Hash路由的优缺点与范围路由相反,Hash路由的优点是表分布比较均匀,缺点是扩容时很麻烦,所有数据均需要重新分布。

  1. 路由配置

  配置路由就是路由表,用一张独立的表来记录路由信息。同样以用户ID为例,我们新增一张ROUTER表,这个表包含table_Id两列,根据user_id就可以查询对应的修改路由表就可以了。 配置路由设计简单,使用起来非常灵活,尤其是在扩充表的时候,只需要迁移指定的数据,然后修改路由表就可以了。 其缺点就是必须多查询一次,会影响整体性能,而且路由表本身如果太大,性能会成为瓶颈点,如果我们再将路由表分库分表,则又面临一个死循环。

(4)结合项目使用

比如在电商网站中, SPU商品表字段过多, 可以采用垂直切分的方式, 将一张表垂直切分从多张表.

SKU库存表由于数据较多可以采用水平切分的方式, 进行水平切分库内分表或者分库分表, 具体需要根据企业业务和数据量详细分析.

 

题目4: 如何保证缓存和数据库信息一致

(1)问题分析

你只要用缓存,就可能会涉及到缓存与数据库双存储双写,你只要是双写,就一定会有数据一致性的问题,双写也就是既要向数据库中写入, 也要向缓存中写入, 称为双写.

注意: 如果不是要求缓存和数据库中的数据强一致性, 最好不要这么做, 因为会降低吞吐量.

一般来说,如果允许缓存可以稍微的跟数据库偶尔有不一致的情况,也就是说如果你的系统不是严格要求 “缓存+数据库” 必须保持一致性的话,最好不要做这个方案,即:读请求和写请求串行化,串到一个内存队列里去。

串行化可以保证一定不会出现不一致的情况,但是它也会导致系统的吞吐量大幅度降低,用比正常情况下多几倍的机器去支撑线上的一个请求。

缓存和数据库同步数据方案

最经典的缓存+数据库读写的模式,就是 Cache Aside Pattern。

  1. 读的时候,先读缓存,缓存没有的话,就读数据库,然后取出数据后放入缓存,同时返回响应。
  2. 更新的时候,先更新数据库,然后再删除缓存。

为什么是删除缓存,而不是更新缓存?

原因很简单,很多时候,在复杂点的缓存场景,缓存不单单是数据库中直接取出来的值。

比如可能更新了某个表的一个字段,然后其对应的缓存,是需要查询另外两个表的数据并进行运算,才能计算出缓存最新的值的。

另外更新缓存的代价有时候是很高的。是不是说,每次修改数据库的时候,都一定要将其对应的缓存更新一份?也许有的场景是这样,但是对于比较复杂的缓存数据计算的场景,就不是这样了。如果你频繁修改一个缓存涉及的多个表,缓存也频繁更新。但是问题在于,这个缓存到底会不会被频繁访问到?

举个栗子,一个缓存涉及的表的字段,在 1 分钟内就修改了 20 次,或者是 100 次,那么缓存更新 20 次、100 次;但是这个缓存在 1 分钟内只被读取了 1 次,有大量的冷数据。实际上,如果你只是删除缓存的话,那么在 1 分钟内,这个缓存不过就重新计算一次而已,开销大幅度降低。用到缓存才去算缓存。

其实删除缓存,而不是更新缓存,就是一个 lazy 计算的思想,不要每次都重新做复杂的计算,不管它会不会用到,而是让它到需要被使用的时候再重新计算。像 mybatis,hibernate,都有懒加载思想。查询一个部门,部门带了一个员工的 list,没有必要说每次查询部门,都里面的 1000 个员工的数据也同时查出来啊。80% 的情况,查这个部门,就只是要访问这个部门的信息就可以了。先查部门,同时要访问里面的员工,那么这个时候只有在你要访问里面的员工的时候,才会去数据库里面查询 1000 个员工。

  1. 核心答案讲解

1)  最初级的缓存不一致解决方案

  1. 问题描述:

先修改数据库,再删除缓存。如果删除缓存失败了,那么会导致数据库中是新数据,缓存中是旧数据,数据就出现了不一致。

  1. 解决思路:

先删除缓存,再修改数据库。如果数据库修改失败了,那么数据库中是旧数据,缓存中是空的,那么数据不会不一致。因为读的时候缓存没有,则读数据库中旧数据,然后更新到缓存中。

2)  高并发下缓存不一致解决方案

  1. 问题描述:

数据发生了变更,先删除了缓存,然后要去修改数据库,此时还没修改。一个请求过来,去读缓存,发现缓存空了,去查询数据库,查到了修改前的旧数据,放到了缓存中。随后数据变更的程序完成了数据库的修改。完了,数据库和缓存中的数据不一样了...

为什么上亿流量高并发场景下,缓存会出现这个问题?

只有在对一个数据在并发的进行读写的时候,才可能会出现这种问题。其实如果说你的并发量很低的话,特别是读并发很低,每天访问量就 1 万次,那么很少的情况下,会出现刚才描述的那种不一致的场景。但是问题是,如果每天的是上亿的流量,每秒并发读是几万,每秒只要有数据更新的请求,就可能会出现上述的数据库+缓存不一致的情况。

  1. 解决思路:
    1. 通过分布式事务的最终一致性, 2PC等分布式事务方案保证一致性.
    2. 缓存系统适用的场景就是非强一致性的场景,所以它属于CAP中的AP,BASE理论。所以尽量使用在非强一致性场景.

(3)问题扩展

缓存的使用一般大概分为两种场景: 强一致性, 非强一致性

  1. 缓存的使用场景尽量使用在非强一致性场景:

更新数据库和缓存数据的顺序, 应该是: 清除缓存数据, 更新数据到数据库.

读取数据顺序: 先读缓存, 缓存中没有数据则到数据库中读取数据, 将数据库中读取到的数据存入缓存, 返回读取的数据结果.

  1. 缓存强一致性使用场景:

使用分布式事务保证缓存和数据库中数据的一致性, 但是这会降低系统的吞吐量. 影响性能. 慎用!

(4)结合项目使用

在电商中消费者使用的系统首页, 都有广告数据, 广告数据更新频繁, 读取更新频繁, 使用缓存解决方案:

1. 更新广告数据:

由管理员系统发起请求操作, 先清除对应的缓存中的数据, 再将数据更新到数据库, 最后将数据更新到缓存.

2.读取广告数据:

由消费者系统发起请求操作, 先读取缓存中的广告数据, 有则返回数据, 没有则返回空.

理解:

广告数据的缓存使用采用了弱一致性方案, 这样不会降低系统的吞吐量

广告数据的更新采用缓存预热流程, 也就是读取的时候不触及数据库, 目的是为了防止缓存穿透问题的发生.

题目5:具体讲解一下JVM和GC机制

  1. 问题分析

面试官主要考察这么几个方面的能力:

  1. 对JVM的架构、基本特性的掌握情况
  2. 对JVM垃圾回收机制的掌握情况
  3. 对JVM性能优化的掌握情况

 

  1. 核心答案讲解
  1. 系统虚拟机与程序虚拟机

虚拟机(Virtual Machine),就是一台虚拟的计算机。它是一款软件,用来执行一系列虚拟计算机指令。大体上,虚拟机可以分为系统虚拟机和程序虚拟机。

 

  • 系统虚拟机:是对物理计算机的仿真,提供了一个可运行完整操作系统的软件平台,比如我们常见的Visaual Box、VMware就属于系统虚拟机。
  • 程序虚拟机:专门为执行单个计算机程序而设计,比如最典型的代表Java虚拟机(JVM),在Java虚拟机中执行的指令我们称为Java字节码指令。

 

  1. JDK与JVM

JDK(Java Development Kit) 是 Java 语言的软件开发工具包(SDK)。JDK 物理存在,是 Java Language、Tools、JRE 和 JVM 的一个集合。

 

  1. JVM的种类

目前主流的java虚拟机有如下几种:

  1. Sun HotSpot VM

它是 Sun JDK 和 OpenJDK 中所带的虚拟机,也是目前使用范围最广的 Java 虚拟机;继承了 Sun 之前两款商用虚拟机的优点(如准确式内存管理),也使用了许多自己新的技术优势,如热点代码探测技术(通过执行计数器找出最具有编译价值的代码,然后通知 JIT 编译器以方法为单位进行编译;

Oracle 公司分别收购了 BEA 和 Sun,并在 JDK8 的时候,整合了 JRokit VM 和 HotSpot VM,如使用了 JRokit 的垃圾回收器与 MissionControl 服务,使用了 HotSpot 的 JIT 编译器与混合的运行时系统。

 

大部分时候我们Java领域讨论的JVM一般情况下都指的是Sun HotSpot VM。

  1. BEA JRockit VM

专注于服务器端应用,内部不包含解析器实现;号称是世界上最快的JVM

  1. IBM J9 VM

全称:IBM Technology for Java Virtual Machine,简称IT4J,内部代号:J9

市场定位于HotSpot接近,服务器端、桌面应用、嵌入式等多用途VM

 

  1. JVM是什么
  • JVM 是 Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。
  • Java虚拟机是二进制字节码的运行环境,负责装载字节码到其内部,解释/编译为对应平台的机器指令执行。每一条Java指令,Java虚拟机规范中都有详细定义,如怎么取操作数,怎么处理操作数,处理结果放在哪里。
  • JMV的特点是:一次编译,到处运次(跨平台)、自动内存管理、自动垃圾回收功能
  • JVM的结构:类加载器子系统、运行时数据区和执行引擎 。

 

  1. 一次编译到处执行如何实现

java程序经过一次编译之后,将java代码编译为字节码也就是class文件,然后在不同的操作系统上依靠不同的java虚拟机进行解释,最后再转换为不同平台的机器码,最终得到执行。如下图:

  1. JVM架构图

从这个架构图不难看出,class文件被jvm装载以后,经过jvm的内存空间调配,最终是由执行引擎完成class文件的执行。当然这个过程还有其他角色模块的协助,这些模块协同配合才能让一个java程序成功的运行。

  1. 类加载器子系统

Java的动态类加载功能是由类加载器子系统处理。当它在运行时(不是编译时)首次引用一个类时,它加载、链接并初始化该类文件。

  1. 运行时数据区(Runtime Data Area)

The 运行时数据区域被划分为5个主要组件:

 

方法区(Method Area)

 

所有类级别数据将被存储在这里,包括静态变量。每个JVM只有一个方法区,它是一个共享的资源。

 

堆区(Heap Area)

 

所有的对象和它们相应的实例变量以及数组将被存储在这里。每个JVM同样只有一个堆区。由于方法区和堆区的内存由多个线程共享,所以存储的数据不是线程安全的。

 

栈区(Stack Area)

 

对每个线程会单独创建一个运行时栈。对每个函数呼叫会在栈内存生成一个栈帧(Stack Frame)。所有的局部变量将在栈内存中创建。栈区是线程安全的,因为它不是一个共享资源。栈帧被分为三个子实体:

  • 局部变量数组 – 包含多少个与方法相关的局部变量并且相应的值将被存储在这里。
  • 操作数栈 – 如果需要执行任何中间操作,操作数栈作为运行时工作区去执行指令。
  • 帧数据 – 方法的所有符号都保存在这里。在任意异常的情况下,catch块的信息将会被保存在帧数据里面。

 

PC寄存器

每个线程都有一个单独的PC寄存器来保存当前执行指令的地址,一旦该指令被执行,pc寄存器会被更新至下条指令的地址。

 

本地方法栈

本地方法栈保存本地方法信息。对每一个线程,将创建一个单独的本地方法栈。

 

  1. JVM的类加载器
  1. JVM自带的类加载器

启动类加载器(Bootstrap)

  • C++编写的,程序员无法在程序中获取该类
  • 负责加载虚拟机的核心库,比如java.lang.Object
  • 没有继承ClassLoader类

扩展类加载器(Extension)

  • Java编写的,从指定目录中加载类库
  • 父加载器是根类加载器
  • 是ClassLoader的子类
  • 如果用户把创建的jar文件放到指定目录中,也会被扩展加载器加载。

系统加载器(System)或者应用加载器(App)

  • Java编写的
  • 父加载器是扩展类加载器
  • 从环境变量或者class.path中加载类
  • 是用户自定义类加载的默认父加载器
  • 是ClassLoader的子类

 

用户自定义的类加载器

  • Java.lang.ClassLoader类的子类
  • 用户可以定制类的加载方式
  • 父类加载器是系统加载器
  • 编写步骤

1、继承ClassLoader

2、重写findClass方法。从特定位置加载class文件,得到字节数组,然后利用defineClass把字节数组转化为Class对象

  • 为什么要自定义类加载器

1、可以从指定位置加载class文件,比如说从数据库、云端加载class文件

2、加密:Java代码可以被轻易的反编译,因此,如果需要对代码进行加密,那么加密以后的代码,就不能使用Java自带的ClassLoader来加载这个类了,需要自定义ClassLoader,对这个类进行解密,然后加载。

  1. JVM的堆

Java堆的内存划分如图所示,分别为年轻代、Old Memory(老年代)、Perm(永久代)。其中在Jdk1.8中,永久代被移除,使用MetaSpace代替。

  • 新生代:
    1、使用复制清除算法(Copinng算法),原因是年轻代每次GC都要回收大部分对象。新生代里面分成一份较大的Eden空间和两份较小的Survivor空间。每次只使用Eden和其中一块Survivor空间,然后垃圾回收的时候,把存活对象放到未使用的Survivor(划分出from、to)空间中,清空Eden和刚才使用过的Survivor空间。
    2、分为Eden、Survivor From、Survivor To,比例默认为8:1:1
    3、内存不足时发生Minor GC
  • 老年代:
    采用标记-整理算法(mark-compact),原因是老年代每次GC只会回收少部分对象。
  • Perm:用来存储类的元数据,也就是方法区。
    1、Perm的废除:在jdk1.8中,Perm被替换成MetaSpace,MetaSpace存放在本地内存中。原因是永久代进场内存不够用,或者发生内存泄漏。
    2、MetaSpace(元空间):元空间的本质和永久代类似,都是对JVM规范中方法区的实现。不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。
  • 堆内存划分示意图

  1. JVM的生命周期
  1. 虚拟机的启动

Java虚拟机的启动是通过引导类加载器(Bootstrap Class Loader)创建一个初始类(initial class)来完成的,这个类是由虚拟机的具体实现指定的。

 

  1. 虚拟机的执行

一个运行中的Java虚拟机有着一个清晰的任务:执行Java程序程序开始执行时它才运行,程序结束时它就停止执行一个所谓的Java程序的时候,真正执行的是一个叫做Java虚拟机的进程你在同一台机器上运行三个程序,就会有三个运行中的Java虚拟机。Java虚拟机总是开始于一个main()方法,这个方法必须是公有、返回void、只接受一个字符串数组。在程序执行时,你必须给Java虚拟机指明这个包含main()方法的类名。

 

  1. 虚拟机的退出

程序正常执行结束

  • 程序在执行过程中遇到了异常或错误而异常终止
  • 由于操作系统出现错误而导致Java虚拟机进程终止
  • 某线程调用Runtime类或System类的exit方法,或Runtime类的halt方法,并且Java安全管理器也允许这次exit或halt操作
  • 除此之外,JNI(Java Native Interface)规范描述了用JNI Invocation API来加载或卸载Java虚拟机时,Java虚拟机的退出情况

 

  1. 垃圾回收机制
  1. 垃圾回收原理

GC (Garbage Collection)的基本原理:将内存中不再被使用的对象进行回收,GC中用于回收的方法称为收集器,由于GC需要消耗一些资源和时间,Java在对对象的生命周期特征进行分析后,按照新生代、旧生代的方式来对对象进行收集,以尽可能的缩短GC对应用造成的暂停。

 

不同的对象引用类型, GC会采用不同的方法进行回收,JVM对象的引用分为了四种类型:

 

  • 强引用:默认情况下,对象采用的均为强引用(这个对象的实例没有其他对象引用,GC时才会被回收)
  • 软引用:软引用是Java中提供的一种比较适合于缓存场景的应用(只有在内存不够用的情况下才会被GC)
  • 弱引用:在GC时一定会被GC回收
  • 虚引用:由于虚引用只是用来得知对象是否被GC

 

  1. 垃圾回收类型
  • Minor GC:从年轻代(包括Eden、Survivor区)回收内存。

1、当JVM无法为一个新的对象分配内存的时候,越容易触发Minor GC。所以分配率越高,内存越来越少,越频繁执行Minor GC

2、执行Minor GC操作的时候,不会影响到永久代(Tenured)。从永久代到年轻代的引用,被当成GC Roots,从年轻代到老年代的引用在标记阶段直接被忽略掉。

  • Major GC:清理整个老年代,当eden区内存不足时触发。
  • Full GC:清理整个堆空间,包括年轻代和老年代。当老年代内存不足时触发。程序中主动调用System.gc()强制执行的GC也会触发Full GC。

 

  1. 垃圾回收基本原则
  • minor垃圾回收器中,最大量的对象被回收,被称为minor gc回收原则。

坚持这个原则可以减少由应用产生的fullgc垃圾回收数量和频率,fullgc往往需要更长的时间,以至于应用无法达到延迟和吞吐量的需求

  • 更多的内存分配给垃圾回收器,也就是说更大的java堆空间,垃圾回收器和应用在吞吐量和延迟上会表现的更好,这条原则被称为gc最大内存原则。
  • 优化jvm垃圾回收器的三个指标中的2个,这个被称为2/3gc优化原则
  1. 垃圾回收算法

常见的垃圾回收算法:

  • Mark-Sweep(标记-清除算法):
  • 思想:标记清除算法分为两个阶段,标记阶段和清除阶段。标记阶段任务是标记出所有需要回收的对象,清除阶段就是清除被标记对象的空间。
  • 优缺点:实现简单,容易产生内存碎片

 

  • Copying(复制清除算法):
  • 思想:将可用内存划分为大小相等的两块,每次只使用其中的一块。当进行垃圾回收的时候了,把其中存活对象全部复制到另外一块中,然后把已使用的内存空间一次清空掉。
  • 优缺点:不容易产生内存碎片;可用内存空间少;存活对象多的话,效率低下。

 

  • Mark-Compact(标记-整理算法)
  • 思想:先标记存活对象,然后把存活对象向一边移动,然后清理掉端边界以外的内存。
  • 优缺点:不容易产生内存碎片;内存利用率高;存活对象多并且分散的时候,移动次数多,效率低下

 

  • 分代收集算法:(目前大部分JVM的垃圾收集器所采用的算法)
  • 思想:把堆分成新生代和老年代。(永久代指的是方法区)

1、因为新生代每次垃圾回收都要回收大部分对象,所以新生代采用Copying算法。新生代里面分成一份较大的Eden空间和两份较小的Survivor空间。每次只使用Eden和其中一块Survivor空间,然后垃圾回收的时候,把存活对象放到未使用的Survivor(划分出from、to)空间中,清空Eden和刚才使用过的Survivor空间。

2、由于老年代每次只回收少量的对象,因此采用mark-compact算法。

3、在堆区外有一个永久代。对永久代的回收主要是无效的类和常量

 

  1. 垃圾收集器种类

新生代的收集器

  • Serial
  • PraNew
  • Parallel Scavenge

 

老年代的收集器

  • Serial Old
  • Parallel Old
  • CMS

 

整个Java堆(新生代和老年代)

 

  • G1收集器

 

G1收集器-标记整理算法,JDK1.7后全新的回收器, 用于取代CMS收集器。

 

最新ShenandoahGC收集器

Jdk12引入了ShenandoahGC

  • 与G1 GC相比,G1的evacuation是parallel的但不是concurrent,而Shenandoah的evacuation是concurrent,因而能更好地减少pause time
  • 与G1 GC一样,ShenandoahGC也是基于region的GC,不同的是ShenandoahGC在逻辑上没有分代,因而就没有young/old

 

  1. 问题扩展
  1. GC日志排查工具
  • jClarity Censum
  • GCViewer

 

  1. JVM常见问题
    1. java堆也是线程共享的区域,我们的类的实例就放在这个区域,可以想象你的一个系统会产生很多实例,因此java堆的空间也是最大的。如果java堆空间不足了,程序会抛出OutOfMemoryError异常。

 

    1. java栈是每个线程私有的区域,它的生命周期与线程相同,一个线程对应一个java栈,每执行一个方法就会往栈中压入一个元素,这个元素叫“栈帧”,而栈帧中包括了方法中的局部变量、用于存放中间状态值的操作栈,这里面有很多细节,我们以后再讲。如果java栈空间不足了,程序会抛出StackOverflowError异常,想一想什么情况下会容易产生这个错误,对,递归,递归如果深度很深,就会执行大量的方法,方法越多java栈的占用空间越大。

 

    1. 每个帧代表一个方法,Java方法有两种返回方式,return和抛出异常,两种方式都会导致该方法对应的帧出栈和释放内存。

 

  1. JVM性能指标
    1. 吞吐:衡量垃圾回收器运行在性能峰值的时候不需要关心垃圾回收期暂停的时间或者需要占用内存的能力
    2. 延迟:衡量垃圾回收器最小化甚至消灭由垃圾回收器引起的暂停时间和应用抖动的能力
    3. 容量:衡量为了高效的运行,垃圾回收器需要的内存

 

一项指标的提升,往往需要牺牲其他一项或者两项指标。换一句话说,一项指标的妥协通常是为了支持提升其他一项或者两项指标。然而,对于大多数应用来说,很少有三项指标都非常重要,通常,一项或者两项比其他的更重要由于始终需要各种权衡,那么知道哪项指标对应用是最有必要的就显得非常重要。

  1. JVM性能优化工具
  1. JDK内置命令行工具
    1. jps(Java Process Status):输出JVM中运行的进程状态信息(现在一般使用jconsole)
    2. jstack:查看java进程内线程的堆栈信息。
    3. jmap:用于生成堆转存快照
    4. jhat:用于分析jmap生成的堆转存快照(一般不推荐使用,而是使用Ecplise Memory Analyzer)
    5. jstat是JVM统计监测工具。可以用来显示垃圾回收信息、类加载信息、新生代统计信息等。

 

  1. JDK内置图形化工具
    1. Jconsole
    2. Jvisualvm
    3. Jmc

 

  1. 结合项目中使用
  1. FullGC问题排查方案
    1. 首先用命令查看触发GC的原因是什么 jstat –gccause 进程id
    2. 如果是System.gc(),则看下代码哪里调用了这个方法
    3. 如果是heap inspection(内存检查),可能是哪里执行jmap –histo[:live]命令
    4. 如果是GC locker,可能是程序依赖的JNI库的原因

 

  1. JVM性能调优方案
  • 一般来说,当survivor区不够大或者占用量达到50%,就会把一些对象放到老年区。通过设置合理的eden区,survivor区及使用率,可以将年轻对象保存在年轻代,从而避免full GC,使用-Xmn设置年轻代的大小
  • 对于占用内存比较多的大对象,一般会选择在老年代分配内存。如果在年轻代给大对象分配内存,年轻代内存不够了,就要在eden区移动大量对象到老年代,然后这些移动的对象可能很快消亡,因此导致full GC。通过设置参数:-XX:PetenureSizeThreshold=1000000,单位为B,标明对象大小超过1M时,在老年代(tenured)分配内存空间。
  • 一般情况下,年轻对象放在eden区,当第一次GC后,如果对象还存活,放到survivor区,此后,每GC一次,年龄增加1,当对象的年龄达到阈值,就被放到tenured老年区。这个阈值可以同构-XX:MaxTenuringThreshold设置。如果想让对象留在年轻代,可以设置比较大的阈值。
  • 设置最小堆和最大堆:-Xmx和-Xms稳定的堆大小堆垃圾回收是有利的,获得一个稳定的堆大小的方法是设置-Xms和-Xmx的值一样,即最大堆和最小堆一样,如果这样子设置,系统在运行时堆大小理论上是恒定的,稳定的堆空间可以减少GC次数,因此,很多服务端都会将这两个参数设置为一样的数值。稳定的堆大小虽然减少GC次数,但是增加每次GC的时间,因为每次GC要把堆的大小维持在一个区间内。
  • 一个不稳定的堆并非毫无用处。在系统不需要使用大内存的时候,压缩堆空间,使得GC每次应对一个较小的堆空间,加快单次GC次数。基于这种考虑,JVM提供两个参数,用于压缩和扩展堆空间。

1、-XX:MinHeapFreeRatio 参数用于设置堆空间的最小空闲比率。默认值是40,当堆空间的空闲内存比率小于40,JVM便会扩展堆空间

2、-XX:MaxHeapFreeRatio 参数用于设置堆空间的最大空闲比率。默认值是70, 当堆空间的空闲内存比率大于70,JVM便会压缩堆空间。

3、当-Xmx和-Xmx相等时,上面两个参数无效

  • 通过增大吞吐量提高系统性能,可以通过设置并行垃圾回收收集器。

1、-XX:+UseParallelGC:年轻代使用并行垃圾回收收集器。这是一个关注吞吐量的收集器,可以尽可能的减少垃圾回收时间。

2、-XX:+UseParallelOldGC:设置老年代使用并行垃圾回收收集器。

  • 尝试使用大的内存分页:使用大的内存分页增加CPU的内存寻址能力,从而系统的性能。-XX:+LargePageSizeInBytes 设置内存页的大小
  • 使用非占用的垃圾收集器。-XX:+UseConcMarkSweepGC老年代使用CMS收集器降低停顿。
  • -XXSurvivorRatio=3,表示年轻代中的分配比率:survivor:eden = 2:3
  • -PretenureSizeThreshold: 直接晋升到老年代的对象大小,设置这个参数后,大于这个参数的对象将直接在老年代分配。
  • -MaxTenuringThrehold: 晋升到老年代的对象年龄。每个对象在坚持过一次Minor GC之后,年龄就会加1,当超过这个参数值时就进入老年代。
  • -UseAdaptiveSizePolicy: 在这种模式下,新生代的大小、eden 和 survivor 的比例、晋升老年代的对象年龄等参数会被自动调整,以达到在堆大小、吞吐量和停顿时间之间的平衡点。在手工调优比较困难的场合,可以直接使用这种自适应的方式,仅指定虚拟机的最大堆、目标的吞吐量 (GCTimeRatio) 和停顿时间 (MaxGCPauseMills),让虚拟机自己完成调优工作。
  • -SurvivorRattio: 新生代Eden区域与Survivor区域的容量比值,默认为8,代表Eden: Suvivor= 8: 1。
  • -XX:ParallelGCThreads:设置用于垃圾回收的线程数。通常情况下可以和 CPU 数量相等。但在 CPU 数量比较多的情况下,设置相对较小的数值也是合理的。
  • -XX:MaxGCPauseMills:设置最大垃圾收集停顿时间。它的值是一个大于 0 的整数。收集器在工作时,会调整 Java 堆大小或者其他一些参数,尽可能地把停顿时间控制在 MaxGCPauseMills 以内。

-XX:GCTimeRatio:设置吞吐量大小,它的值是一个 0-100 之间的整数。假设 GCTimeRatio 的值为 n,那么系统将花费不超过 1/(1+n) 的时间用于垃圾收集。

 

题目6:分布式架构与微服务架构的对比、区别  

  1. 问题分析

面试官主要考核求职者这么几个方面的能力:

  • 对分布式的背景、分布式的特点、分布式的问题的掌握情况;
  • 对单体结构、分布式架构和微服务架构的对比掌握的情况;
  • 对分布式架构和微服务使用经验;

 

  1. 核心答案讲解

 

  1. 为什么会有分布式

分布式系统是由集中式系统(也称单体系统)演进过来的,也就是所有的程序和功能部署在一台服务器上,对外提供服务。这样做的好处是服务管理比较方便。但是问题在于一旦出现服务器宕机,那么就造成单点故障,也就没法正常对外提供服务。原因在于单个节点服务由于受限于单台服务器的硬件性能瓶颈,在流量爆发增长的时候,这个单节点服务的计算和存储压力会越来越大,当服务承受不足压力时就会宕机。

那么怎样能做到避免单点故障呢?那么常规的做法只有是升级服务器的硬件,比如加内存、加磁盘、使用更好的CPU,这个时候虽然能在短时间内解决服务器的压力,但是流量越大,升级也就越频繁,反复升级会造成给企业带来高昂的成本开销。

有没有更成本更低效率更高的解决方案?是有的,我们可以将服务进行多节点服务器部署,将大量的计算和存储压力交给更多的服务器来并行处理,提升性能。那么这种方式就是我们常说的分布式。分布式的出现是为了用廉价的、普通的机器完成单个机器无法完成的计算、存储任务,从而使得并发请求的处理效率能够提升。

总结来说我们之所以需要分布式系统,是为了摆脱单机资源的束缚。再具体点,是为了解决这两个问题:

  • 单台机器算的慢,哪怕协程、多线程、多进程全用上
  • 单台机器装不下,哪怕磁盘扩容在多次最终仍然会达到上限

 

分布式系统中最基础核心的是解决两种问题:

  • 分布式计算,用来解决算的慢的问题
  • 分布式存储,用来解决存不下的问题

 

综上所述,我们说的分布式一般指的是部署层面的分布式。而软件开发的分布式则属于架构领域的分布式,详见下方说明。

 

  1. 单体架构、分布式架构和微服务架构的区别

单体架构系统一般的意义指的是将系统设计成一个项目,所有开发人员在这一个项目上进行迭代开发,项目开发完只有一个部署包。这种架构的常见问题在于:

  • 开发进度很慢(有时牵一发而动全身,依赖太多导致启动太慢)
  • 扩展新功能麻烦(代码耦合度太高)
  • 系统性能差(任何一块代码出现的内存泄露等问题都可能造成服务崩溃)
  • 迭代更新慢(由于开发慢、测试用例执行时间长,造成发布升级非常耗时)

 

分布式架构一般的意义指的是将一个集中式架构的系统(项目),按照业务维度拆分成N多个独立的系统,每个系统都由独立的开发团队进行开发,开发测试完毕后可以单独部署到多个节点,各系统之间相互协作对外为用户提供服务。这个拆分过程也是我们常说的服务模块化。这种架构的优势在于:

 

  • 开发进度快(各开发团队负责自己的系统)
  • 扩展功能简单(按照业务划分的模块,添加新功能模块很方便)
  • 系统性能好(系统独立部署,服务间互相通信、分布式部署服务可用性高)
  • 迭代更新快(如果只改动某个模块,那么该模块系统升级即可,操作便捷)

 

微服务架构是建立在分布式架构的基础之上的,本质也是分布式架构系统,仍然需要解决分布式架构系统带来的一系列问题。微服务架构拆分的系统也是按照业务维度拆分,拆分的各个系统我们常称为微服务组件。微服务架构相对于一般分布式架构系统不同地方在于:

  • 精细化:就是常说的单一职责,一个组件只做一类事,拆分力度比微服务架构更细。每个微服务都由独立的小团队负责,服务扩展更灵活,能够更快速更高效的响应产品的迭代更新。
  • 轻量级通信(HTTP):能跨多编程语言进行团队协作,各编程语言团队保持API契约即可。
  • 去中心化管理:不同于分布式架构会要求项目的技术栈完全统一、测试统一,部署统一。微服务的每个团队都可以自主管理(也称小而完备的自主团队),自行负责各组微服务里的技术选型、测试、部署。
  • 自动化部署:由于微服务组件拆分的太多,那么部署就不能是简单的手动部署,为了响应快速迭代的要求,微服务强调要使用自动化的流程进行服务部署管理。
  • 容器化部署:微服务及其依赖的其他服务通常以容器的形式运行。
  • 使用网关:微服务架构里网关至关重要,作为所有请求的总入口,担负着请求转发、限流、鉴权、请求链路追踪等重要功能。

 

 

  1. Java领域架构技术栈的对比
  • 大多数分布式架构系统常基于spring、springmvc、mybatis等框架来进行开发,服务之间多采用dubbo进行通信。
  • 大多数的微服务架构架构系统使用springboot、springcloud、mybatis框架进行开发、服务之间多采用springcloud eureka进行通信。而eureka通信本质是http通信,所以完全符合微服务强调的轻量级通信。使用spirngcloud的好处在于它提供了很多开源稳定的高效套件,比如网关、服务发现、熔断器、配置中心等。

 

最后说一点,分布式架构服务最后都会向微服务架构演化,这是一种趋势,不过服务微服务化后带来的挑战也是显而易见的,例如服务粒度小,数量大,后期运维将会很难。

 

  1. 问题扩展

无论是微服务架构系统还是一般的分布式架构系统,那么都需要解决分布式带来的问题,常见的分布式问题如下:

  • 节点故障问题:虽然单个节点的故障概率较低,但节点数目达到一定规模,出故障的概率就变高了。分布式系统需要保证故障发生的时候,系统仍然是可用的,这就需要监控节点的状态,在节点故障的情况下将该节点负责的计算、存储任务转交到其他节点负责。

 

  • 网络通信问题:节点间通过网络通信,而网络是不可靠的。可能的网络问题包括:网络分割、延时、丢包、乱序。也就是咱们常说的为什么CAP中的P是无法满足的,就在于此。

 

  • 服务部署问题:多节点服务一旦面临系统升级,需要有一种比较好的方案确保升级系统期间对用户不造成任何影响,如果升级失败能否快速回滚。

 

  • 服务监控问题:由于服务部署在多个节点上,统一的业务数据监控、异常告警监控并没有那么容易实现,需要有对应的解决方案。
  • 服务容错问题:由于服务非常多,往往是一个服务挂了,整个请求链路的服务都受到影响,因此需要服务容错,在服务调用失败的时候能够处理错误或者快速失败,包括熔断、fallback、重试、流控和服务隔离等方面的问题都需要考虑周全。

 

  • 分布式事务问题:由于应用程序部署在多个节点上,那么每个节点的事务都属于独立的事务,事务控制在每个服务内生效;分布式环境下用户的一次请求会经过很多微服务,如何保证一个完整的业务处理中数据的一致性,需要有相应的分布式事务解决方案。

 

  • 分布式锁问题:如何确保分布式环境下操作业务数据的原子性,需要有对应的锁解决方案。

 

  • 分布式session问题:不同于单节点应用,如何保证用户在分布式节点中某个节点登录成功后再次请求到其他节点仍然拥有session,需要有对应的session解决方案。

 

  1. 结合项目中使用
  • 分布式事务解决方案:在互联网企业开发过程中最好用,也是最容易被接收的分布式事务解决方案是最终一致性方案,也就是多节点服务间基于mq实现消息的发送或补偿发送,确保消息发送和消费不丢失,最终业务程序能处理成功和失败的所有数据,从而达到数据一致性。
  • 分布式锁解决方案:在互联网企业开发过程中,最常用的锁属于redis的setnx锁,通过setnx能保证操作某一个业务时如果出现并发能保证操作的原子性。
  • 分布式session解决方案:在互联网开发过程中,最常见的session解决方案,是使用利用token机制代替session,相当于为登录成功的用户生成一个带有过期时间的令牌(token),使用JWT技术就能达到生成令牌的目的。用户登录后访问其他节点,这些节点只需要校验此令牌是否和合法即可。

 

题目7:  SQL优化

  1. 问题分析

 

考官主要针对sql调优思路的考核. 如何处理突然的业务办理卡顿? 如何进行常规调优?

 

  1. 核心答案讲解

 

  1. 应急调优的思路:

针对突然的业务办理卡顿,无法进行正常的业务处理!需要立马解决的场景!

  1. show processlist(查看链接session状态)

  1. command:显示当前连接的执行的命令,一般就是休眠 sleep ,查询 query ,连接connect
  2. time:此这个状态持续的时间,单位是秒。
  3. state:显示使用当前连接的 sql 语句的状态,很重要的列,后续会有所有的状态的描述, state 只是语句执行中的某一个状态,一个 sql 语 句可能需要经过 copying to tmp table , Sorting result , Sending data 等状态才 可以完成; 当如果用户某个SQL正在query导致别的SQL等待锁的时候,别的SQL的state状态会出现”Waiting for table metadata lock“,如果出现这种情况就会导致大量SQL堆积,实例状态出现异常,因此这个时候就需要用户去根据前面的id,去kill掉导致其他SQL等待锁的正在query的语句,然后其他语句即会正常执行。
  4. - info:显示这个 sql 语 句,一个判断问题语句的重要依据
  1. explain(分析查询计划),show index from table(分析索引)
  2. 通过执行计划判断,索引问题(有没有、合不合理)或者语句本身问题
  3. show status like '%lock%'; # 查询锁状态
  4. kill SESSION_ID; # 杀掉有问题的session  SESSION_ID就是show processlist中的ID字段

 

  1. 常规调优的思路:

 

针对业务周期性的卡顿,例如在每天10-11点业务特别慢,但是还能够使用,过了这段时间就好了。

  1. 查看slowlog,分析slowlog,分析出查询慢的语句。
  2. 按照一定优先级,进行一个一个的排查所有慢语句。
  3. 分析top sql,进行explain调试,查看语句执行时间。
  4. 调整索引或语句本身。

 

 

  1. 问题扩展

 

  1. sql语句调优
  1. 避免使用in, 可以使用 between和连接等替换
  2. 避免使用select *, 务必指定字段名称 如: select id,name, age from 表名
  3. 当只有一条sql语句的时候, 使用 limit 1
  4. 如果排序字段没有用到索引, 就减少排序
  5. 如果限制条件中其他字段没有索引, 尽量少用or
  6. 尽量用union all代替union
  7. 不使用ORDER BY RAND()
  8. 避免在where子句中对字段进行null值判断
  9. 不建议使用%前缀模糊查询, 这样会全表扫描
  10. 对于联合索引来说,要遵守最左前缀法则
  11. 必要时可以使用force index来强制查询走某个索引
  12. 注意范围查询语句, 对于联合索引来说,如果存在范围查询,比如between、>、<等条件时,会造成后面的索引字段失效。
  13. 尽量使用inner join,避免left join
  14. 利用小表去驱动大表

 

  1. 索引相关

 

  1. 哪种情况适合创建索引?
  1. 主键约束默认建立唯一索引
  2. 频繁作为查询条件的字段应该创建索引
  3. 多表查询中与其它表进行关联的字段,外键关系建立索引
  4. 单列索引/复合索引的选择? 高并发下倾向于创建复合索引
  5. 查询中经常用来排序的字段
  6. 查询中经常用来统计或者分组字段

 

  1. 哪种情况不适合创建索引?
  1. 频繁更新的字段: 每次更新都会影响索引树
  2. where条件查询中用不到的字段
  3. 表记录太少
  4. 经常增删改的表: 更新了表,索引也得更新才行
  5. 注意: 如果一张表中,重复的记录非常多,为它建立索引就没有太大意义

 

 

  1. 结合项目中使用

 

项目新增条件查询发现周期性的卡顿, 快速定位到具体的慢sql, 调整sql的索引才解决.

题目8Spring框架中的AOP和IoC

  1. 问题分析

 

考官主要想了解你对Spring框架的掌握情况,Spring中最重要的两个内容就是IoC和AOP,此为程序员必须掌握知识点。如果这个2个点不清楚,那么你不懂Spring,如果Spring框架不理解,那么不算是一个合格的Java工程师。

 

  1. 核心答案讲解

 

什么是IoC?

IoC是inverse of control的缩写,译为控制反转,将对象的控制权【new、销毁、对象属性赋值】交给容器。Spring框架的核心就是Spring IoC容器。Spring容器使用依赖注入来管理组成应用程序的Bean对象。容器通过读取提供的配置信息来接收对象进行实例化,配置和组装。Spring容器的配置方式:XML配置文件 (beans.xml)、配置类。

 

说到IoC那不得不提到DI (Dependency Injection)依赖注入。依赖注入与IoC相伴而生。在IoC中,你将对象的创建权交给容器,但必须描述这个对象该如何创建。比如,创建的对象需要哪些成员变量,赋值可以使用构造函数、setter方法,然后由容器进行装配。这我们称之为依赖注入,Spring IoC容器不仅对象控制权反转了,同时也是依赖注入的。

 

IoC和DI有什么区别?

IoC是一个比较宽泛的概念,而DI则更加具体,我们常说的IoC和DI,两者其实很大程度上是在说一件事情。大牛Martin Flower马丁 福勒 , IoC和DI的创始者曾说,IoC太土了很多框架都有,而且不足以体现容器的反转,于是给IoC起了一个新的名字叫Dependency Injection。

 

什么是AOP?

AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,是通过运行期动态代理的技术,实现程序功能统一维护的一种技术。

 

AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了程序开发的效率。

 

通俗的来说:它就是把我们程序重复的代码抽取出来,在需要的时候,利用动态代理,在不修改源码的情况下,对我们的已有方法进行功能的增强。这就相当于方法的拦截器。批量方法功能的动态增强。

 

  1. 问题扩展

 

在OOP中,以类(class)作为基本单元

在AOP中,以切面(Aspect)作为基本单元

 

在Spring AOP的面试题中,不仅仅考察AOP,还会考察对切面基本概念的理解,主要概念有:

  1. Aspect(切面) :由切点和通知组成,它既包含了横切逻辑的定义,也包含了连接点的定义。AOP的工作重心就在于Aspect切面上
  2. JoinPoint(连接点):程序运行中的一些关键点,例如方法执行前后,方法异常时,方法最终执行完毕时。
  3. PointCut (切入点):匹配JoinPoint的条件,也称之为切入点表达式
  4. Advice(通知):特定JoinPoint处的Aspect所采取的动作称之为Advice
  5. Target(目标对象):织入Advice的目标对象。目标对象被称之为Advised Object

 

  1. 结合项目中使用

 

Spring AOP经典应用:日志记录、应用程序性能监控,事务管理,及权限控制。

Spring IoC应用场景:Spring已经是实际意义上的Java编程的行业标准,几乎在Java应用程序中都用到了它。而每个Spring框架内都有IoC容器。不论你知不知道,它就在那里~

题目9: TCP/UDP协议

  1. 问题分析

 

考官主要针对网络协议的考核. 这是因为不管是前端还是后端, 几乎所有的程序运行都会涉及网络协议.

比如你想优化网站的访问速度, 大概思路是减少http请求, 同时设置静态文件缓存时间、压缩情况、保持持久的连接时间等参数, 这些东西需要你系统的了解协议.

 

  1. 核心答案讲解

 

  1. UDP 用户数据报协议

UDP 是 User Datagram Protocol 的简称, 称为用户数据报协议

UDP 是无连接通信协议, 即在数据传输时, 发送端和接收端不建立逻辑连接. 简单来说, 当一台计算机向另外一台计算机发送数据时, 发送端不会确认接收端是否存在, 就会发出数据. 同样接收端在接收数据时, 也不会向发送端反馈是否收到数据. 由于使用 UDP 协议消耗资源小, 通信效率高, 所以通常都会用于音频, 视频和普通数据的传输, 例如视频会议都使用UDP协议, 因为这种情况即使偶尔丢失一两个数据包, 也不会对接收结果产生太大影响. 但是在使用 UDP 协议传送数据时, 由于UDP面向无连接, 不能保证数据的完整性, 因此在传输重要数据时不建议使用 UDP 协议.

特点 : 两端都相同, 可以发送数据, 同时也可以接收数据. 无明显的差异.

 

  1. TCP 传输控制协议

TCP 是 Transmission Control Protocol 的简称, 称为传输控制协议.

TCP协议是面向连接的通信协议, 即在传输数据前先在发送端和接收端建立逻辑连接, 然后再传输数据, 它提供了两台计算机之间可靠无差错的数据传输. 在TCP连接中必须要明确客户端和服务器端, 每次连接的创建都需要经过 `三次握手`, 第一次握手, 客户端向服务端发出连接请求, 等待服务器确认; 第二次握手, 服务器向客户端回送一个响应, 通知客户端收到了连接请求; 第三次握手, 客户端再次向服务器端发送确认信息, 确认连接.

由于TCP协议的面向连接性, 它可以保证传输数据的安全性, 所以是一个被广泛采用的协议, 例如在下载文件时, 如果数据接收不完整, 将会导致文件数据丢失而不能打开, 因此, 下载文件时必须采用TCP协议.

 

 

  1. 问题扩展

 

  1. 两种传输协议的简单对比 :
  1. 使用UDP时, 每个数据包中都给出了完整的地址信息, 因此不需要建立发送方和接收方法的连接.
  2. 对于 TCP 协议, 由于它是一个面向连接的协议, 在Socket之间进行数据传输之前必然要建立连接. 所以在TCP中多了一个连接的建立时间.
  3. 使用UDP传输数据时是有大小限制的, 每个被传输的数据报必须限定在 64KB 之内.
  4. TCP没有这方面的限制, 一旦连接建立起来, 双方的socket就可以按统一的格式传输大量的数据.
  5. UDP是一个不可靠的协议, 发送方所发送的数据报不一定以相同的次序到达接收方.
  6. TCP是一个可靠的协议, 它确保接收方完全正确地获取发送方所发送的全部数据.

 

  1. 结合项目中使用

 

  1. TCP应用场景

效率要求相对低,但对准确性要求相对高的场景。因为传输中需要对数据确认、重发、排序等操作,相比之下效率没有UDP高。举几个例子:文件传输(准确高要求高、但是速度可以相对慢)、接受邮件、远程登录。

 

  1. UDP应用场景

效率要求相对高,对准确性要求相对低的场景。举几个例子:QQ聊天、在线视频、网络语音电话(即时通讯,速度要求高,但是出现偶尔断续不是太大问题,并且此处完全不可以使用重发机制)、广播通信(广播、多播)。

题目10 :

1.问题分析

考官主要是针对基础数据结构的考核.比如hashmap的实现原理或者md5之类hash摘要函数的应用场景问题.

2.核心答案讲解

Hash,一般翻译做散列、杂凑,或音译为哈希,是把任意长度的输入(又叫做预映射pre-image)通过散列算法变换成固定长度的输出,该输出就是散列值。这种转换是一种压缩映射,也就是,散列值的空间通常远小于输入的空间,不同的输入可能会散列成相同的输出,所以不可能从散列值来确定唯一的输入值。简单的说就是一种将任意长度的消息压缩到某一固定长度的消息摘要的函数。

Hash算法可以将一个数据转换为一个标志,这个标志和源数据的每一个字节都有十分紧密的关系。Hash算法还具有一个特点,就是很难找到逆向规律。

Hash算法是一个广义的算法,也可以认为是一种思想,使用Hash算法可以提高存储空间的利用率,可以提高数据的查询效率,也可以做数字签名来保障数据传递的安全性。所以Hash算法被广泛地应用在互联网应用中.

(以上解释来自互联网)

简单解释(白话):

hash就是给出一个原有的数据段 比如说一个字符串(本质也是一个字节数组)或者一个字节数组之类的数据.然后带入某个函数 计算出另外一个值 算出的这个值称为原有数据段的hash值.比如说典型的java语言的计算字符串的hash算法如下:

public int hashCode() {
       int h = hash;
       if (h == 0 && value.length > 0) {
           char val[] = value;

           for (int i = 0; i < value.length; i++) {
               h = 31 * h + val[i];
          }
           hash = h;
      }
       return h;
}
//java语言的计算字符串的hash值采用的乘数累加算法 字符串的每一个字节都参与其中,得出一个数值,一旦某个字节改变了那么得出的hash值也会改变(不一定 有可能会有hash碰撞)

从上面java的字符串hash算法 可以看出hash算法其实就是给出原有值 经过定义好固定的规则进行计算 然后算出另外一个值而已的操作. 用算出来的该值来作为原数据的标识或者代表(想想我们生活中的身份证id)

只不过 算法规则有很多 你也可以发明其他的计算公式 或者改进别人的,比如说我可以31改成33之类进行计算...甚至我不用乘法 我用除法来计算 甚至加减法都是可以的.也就是说法hash算法只是一种思想,你自己来决定如何实践这种思想. 或者复杂或者简单 都可以.

hash碰撞:

自己实现一个hash算法过程 要注意一个事情 叫做hash碰撞 所谓hash碰撞指的是 给出的原有数据不一样 但是由于自己规范的计算规则不好 导致算出的hash值却是一样 我们称这种行为叫做hash碰撞. 一个好的hash算法应该劲量的减少hash碰撞的出现 但是事实上几乎不可避免.

一些常见的hash算法方式:

1.直接寻址法。取关键字或关键字的某个线性函数值为散列地址。即H(key)=key或H(key) = a·key + b,其中a和b为常数(这种散列函数叫做自身函数)

2. 数字分析法。分析一组数据,比如一组员工的出生年月日,这时我们发现出生年月日的前几位数字大体相同,这样的话,出现冲突的几率就会很大,但是我们发现年月日的后几位表示月份和具体日期的数字差别很大,如果用后面的数字来构成散列地址,则冲突的几率会明显降低。因此数字分析法就是找出数字的规律,尽可能利用这些数据来构造冲突几率较低的散列地址。

3. 平方取中法。取关键字平方后的中间几位作为散列地址。

4. 折叠法。将关键字分割成位数相同的几部分,最后一部分位数可以不同,然后取这几部分的叠加和(去除进位)作为散列地址。

5. 随机数法。选择一随机函数,取关键字作为随机函数的种子生成随机值作为散列地址,通常用于关键字长度不同的场合。

6.除留余数法:取keyword被某个不大于散列表表长m的数p除后所得的余数为散列地址。即 H(key) = key MOD p, p<=m。不仅能够对keyword直接取模,也可在折叠、平方取中等运算之后取模。对p的选择非常重要,一般取素数或m,若p选的不好,easy产生同义词.

通过以上方式进而有人把这些方式组合加以改进出现了一些著名的hash算法

Md5 ,md4,sha1,crc系列等等

这些算法中有计算简单(速度快)但是碰撞率高点 有的计算复杂点(计算慢)但是碰撞率低点

3.问题扩展

java语言中字符串hash算法就是采用 乘法和加法运算从上面代码可以看出来.为什么java采用了31作为乘数来做运算而不是2 4 8 甚至11 100啊这样的数字做乘子?

上面介绍hash算法的时候说到 hash算法会发生碰撞 但是我们中心思想就是想用hash值来代表原有数据.如果出现大量的碰撞那么我们hash值其实也就失去标识符的作用.所以一个好的hash算法 应该在保证计算快的前提 尽量减少碰撞的概率.而通过大量的数据测试发现 偶数做乘子的碰撞率非常大 质数要低很多(当然有的偶数其实碰撞率也很低 但是一般情况下 质数碰撞率都较低一些.)

所以我们一般选用质数做乘子 ,而且经过一组数组测试 在可以用的质数17、 29、 31、 33、 37、 61..等等这些质数中 31 和33计算出的hash值 碰撞率 而且分布均匀.所谓均匀就是算出的hash值平均分到0-integer.max_value之间

所以java使用了31 当然33也可以..

4.实际应用场景

现在我们可以自己写出一些hash算法或者用先辈们发明的一些hash算法得到hash值,具体的作业如下:

1. hashmap是我们开发最常用的数据结构,它底层是采用对key进行求hash值对一个数组取余 来获取它要保存的数据的位置,如果遇到碰撞,hashmap使用了链表方式,甚至红黑树的方式。

2. 网络传输链路层的crc校验,为了保证底层网络设备在传输数据的正确性,链路层协议规定了,要对传输的数据传输前先计算一个crc校验码(也就是一个hash值),然后接受到以后再进行计算是否正确来保证链路层传输过程正确性

3.md5算法用来互联网传输数据的安全(防篡改).发送数据之前对数据进行 md5计算出md5值.并且将原有数据和md5一起传输给对方 对方采用相同方式原有数据计算md5值 比对如果一致说明数据没有被黑客篡改.

4.分布式数据存储问题.典型的redis集群.分散数据到集群的各个节点中,分散单点压力.可以利用hash算法 算出key的hash值 根据key的hash值进行求余数选中某个节点进行操作.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值