java面试总结

1、Java基础

1.1、Java 面向对象包括哪些特性, 怎么理解的

封装:装隐藏了类的内部实现机制。对属性进行了封装:外界只能通过特定的方法进行访问。对方法进行了封装:外界只能通过定制好的方式调用,不用了解方法内部逻辑,方便使用。保护了数据。便于修改,增强了代码的可维护性和复用性
继承:继承是从已有的类中派生出新的类,即子类继承自父类。当子类通过extends关键字继承了父类后,便继承了父类的属性和方法(注意:子类继承了父类的所有属性和方法。但父类的私有属性和方法,子类是不能直接访问的,只是拥有但无法使用)。同时子类还可以具备父类所不具备的属性或方法。

通过继承避免了对各个类中重复的属性和方法进行反复描述。增强了代码的复用性。代码更加简洁。

多态:简单的说就是用同样的对象引用调用同样的方法但是做了不同的事情。

要实现多态需要做两件事:
第一: 方法重写( 子类继承父类并重写父类中已有的或抽象的方法) ;
第二: 对象造型( 用父类型引用指向子类型对象, 这样同样的引用调用同样的方法就会根据子
类对象的不同而表现出不同的行为) 。
抽象: 抽象是将一类对象的共同特征总结出来构造类的过程, 包括数据抽象和行为抽象两
方面。 抽象只关注对象有哪些属性和行为, 并不关注这些行为的细节是什么。

1.2、HashMap的底层数据结构是怎样的?

JDK1.8以前

  • JDK1.8以前HashMap底层是数组+链表结合的方式来实现的;
  • HashMap通过 key 的 hashCode 经过扰动函数处理过后得到 hash 值,然后通过 (n -1) & hash来得到存放于数组中的下标位置,如果当前位置存在元素,则判断Hash值和key是否相同,如果相同直接覆盖,不相同就用拉链法来解决冲突;
  • 所谓扰动函数就是HashMap的Hash方法。使用 hash 方法也就是扰动函数是为了防止一些实现比较差的 hashCode() 方法 换句话说使用扰动函数之后可以减少碰撞。

JDK1.8以后

  • 当链表长度大于阈值( 默认为 8) 时, 会首先调用 treeifyBin()方法。 这个方法会根据HashMap 数组来决定是否转换为红黑树。 只有当数组长度大于或者等于 64 的情况下, 才会执行转换红黑树操作, 以减少搜索时间。 否则, 就是只是执行 resize() 方法对数组扩容。

1.3、HashMap的扩容机制

一般情况下, 当元素数量超过阈值时便会触发扩容。 每次扩容的容量都是之前容量的 2 倍。
HashMap 的容量是有上限的, 必须小于 1<<30, 即 1073741824。 如果容量超出了这个
数, 则不再增长, 且阈值会被设置为 Integer.MAX_VALUE。


JDK1.7中的扩容机制:

  • 实例化的 HashMap 默认内部数组是 null, 即没有实例化。 第一次调用 put 方法时, 则会开始第一次初始化扩容, 长度为 16;
  • 平衡因子为0.75,默认阈值为16*0.75=12,大于阈值时候会扩容;
  • 如果不是第一次扩容, 则 新容量=旧容量 x 2 , 新阈值=新容量 x 负载因子 。

JDK1.8中的扩容机制:

  • 如果不是第一次扩容, 则容量变为原来的 2 倍, 阈值也变为原来的 2 倍。 ( 容量和阈值都变为原来的 2 倍时, 负载因子还是不变) ;
  • 链表长度大于8时才会转换为红黑树,红黑树长度小于6时会退化为链表;

1.4、ConcurrentHashMap 的存储结构是怎样的?

Java7 中 ConcurrnetHashMap 使用的分段锁, 也就是每一个 Segment 上同时只有一
个线程可以操作, 每一个 Segment 都是一个类似 HashMap 数组的结构, 它可以扩容,
它的冲突会转化为链表。 但是 Segment 的个数一但初始化就不能改变, 默认 Segment
的个数是 16 个。

Java8 中的 ConcurrnetHashMap 使用的 Synchronized 锁加 CAS 的机制。 结构也由
Java7 中的 Segment 数组 + HashEntry 数组 + 链表 进化成了 Node 数组 + 链表 / 红
黑树, Node 是类似于一个 HashEntry 的结构。 它的冲突再达到一定大小时会转化成红
黑树, 在冲突小于一定数量时又退回链表。

1.6、OOM 问题?

oom原因就是一个,内存不够,所以要细分到底是哪里的内存不够。

栈溢出:栈中存放的是方法调用的出口,局部变量。这种情况很少,一般就是栈调用太深了,比如写了一个超长,无限递归的方法

方法区溢出:方法区存放的是类信息,常量,静态变量。这种情况说明类太多了,考虑加大方法区内存,或者加大jvm,或者反向思考是不是需要拆分服务

堆溢出:堆中存放的是实例的对象,大部分oom都是发生在堆中。通过分析heap dump日志文件。Heap dump文件是一个二进制文件,它保存了某一时刻JVM堆中对象使用情况。Heap Dump文件是指定时刻的Java堆栈的快照,是一种镜像文件。Heap Dump一般都包含了一个堆中的Java Objects, Class等基本信息。

  • 增加两个参数 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/heapdump.hprof, 当 OOM 发生时自动 dump 堆内存信息到指定目录。

  • 同时 jstat 查看监控 JVM 的内存和 GC 情况, 先观察问题大概出在什么区域。

  • 使用 MAT 工具载入到 dump 文件, 分析大对象的占用情况, 比如 HashMap 做缓存未清理, 时间长了就会内存溢出, 可以把改为弱引用。

  • 用MemoryAnalyzer.exe等一些工具分析错误原因,结合代码定位到发生异常的地方。

1.9、ThreadLocal( 线程本地变量) 的底层实现原理及常用场景

hreadLocal 是一个解决线程并发问题的一个类, 用于创建线程的本地变量, 我们知道一个
对象的所有线程会共享它的全局变量, 所以这些变量不是线程安全的, 我们可以使用同步技术。
但是当我们不想使用同步的时候, 我们可以选择 ThreadLocal 变量。 例如, 由于 JDBC 的
连接对象不是线程安全的, 因此, 当多线程应用程序在没有协同的情况下, 使用全局变量时,
就不是线程安全的。 通过将 JDBC 的连接对象保存到 ThreadLocal 中, 每个线程都会拥有
属于自己的连接对象副本。

1.11、垃圾回收

利用可达性分析算法, 虚拟机会将一些对象定义为 GCRoots, 从 GCRoots 出发沿着引用链
向下寻找, 如果某个对象不能通过 GCRoots 寻找到, 虚拟机就认为该对象可以被回收掉。
l 哪些对象可以被看做是 GCRoots 呢?
1) 虚拟机栈( 栈帧中的本地变量表) 中引用的对象;
2) 方法区中的类静态属性引用的对象, 常量引用的对象;
3) 本地方法栈中 JNI(Native 方法) 引用的对象;


1.12、反射

开发过程中, 经常会遇到某个类的某个成员变量、 方法或属性是私有的, 或只
对系统应用开放, 这里就可以利用 java 的反射机制通过反射来获取所需的私有成员或是方法。

  • Class.forName("com.zhenai.api.Apple");
  • 使用 Constructor 对 象 的 newInstance 方 法 获 取 反 射 类 对 象

1.13、注解

注解的作用:
l 提供信息给编译器: 编译器可利用注解来探测错误和警告信息
l 编译阶段: 软件工具可以利用注解信息来生成代码、 html 文档或做其它相应处理;
l 运行阶段: 程序运行时可利用注解提取代码
注解是通过反射获取的, 可以通过 Class 对象的 isAnnotationPresent()方法判断它是否应
用了某个注解, 再通过 getAnnotation()方法获取 Annotation 对象


1.14、String 为什么要设计成不可变的?

因为 String 设计成不可变, 当创建一个 String 对象时, 若此字符串值已经存在于常量池中, 则不会创建一个新的对象, 而是引用已经存在的
对象。
String 对象可以缓存 hashCode。 字符串的不可变性保证了 hash 码的唯一性, 因此可
以缓 存 String 的 hashCode, 这样不用每次去重新计算哈希码。 在进行字符串比较时,
可以直接比较 hashCode, 提高了比较性能;


1.15、Java 怎么实现线程安全?

  • 使用同步代码块
  • 使用同步方法
  • 使用 Lock 锁机制, 通过创建 Lock 对象, 采用 lock()加锁, unlock()解锁, 来保护指定的代码块

1.16、Spring Bean作用域

  • 单例 singleton : bean 在每个 Spring IOC 容器中只有一个实例。
  • 原型 prototype: 一个 bean 的定义可以有多个实例。
  • request: 每次 http 请求都会创建一个 bean。
  • session: 在一个 HTTP Session 中, 一个 bean 定义对应一个实例

1.18、ArrayList 和 LinkedList 的区别在哪里?

  • 数据结构实现: ArrayList : 基于数组, 便于按 index 访问, 超过数组需要扩容, 扩容成本较高。 LinkedList: 使用链表实现, 无需扩容。
  • 随机访问效率: ArrayList 比 LinkedList 在随机访问的时候效率要高, 因为 LinkedList是线性的数据存储方式, 所以需要移动指针从前往后依次查找。
  • 增加和删除效率: 在非首尾的增删操作, LinkedList 要比 ArrayList 效率要高, 因为ArrayList 增删操作要影响数组内的其他数据的下标。
  • 内存空间占用: LinkedList 比 ArrayList 更占内存, 因为 LinkedList 的节点除了存储数据, 还存储了两个引用, 一个指向前一个元素, 一个指向后一个元素。

线程安全: ArrayList 和 LinkList 都是不同步的, 不保证线程安全。

1.19、Synchronized、 ReentrantLock 的区别

  • synchronized 是关键字,Synchronized是依赖于JVM实现的,而ReentrantLock 是 API 接口,ReenTrantLock是JDK实现的
  •  Lock 需要手动加锁, 手动释放锁
  • synchronized 不可中断, ReentrantLock 可中断, ReenTrantLock提供了一种能够中断等待锁的线程的机制,通过lock.lockInterruptibly()来实现这个机制。
  •  ReenTrantLock可以指定是公平锁还是非公平锁。而synchronized只能是非公平锁。所谓的公平锁就是先等待的线程先获得锁。
  •  ReenTrantLock提供了一个Condition(条件)类,用来实现分组唤醒需要唤醒的线程们,而不是像synchronized要么随机唤醒一个线程要么唤醒全部线程。

可重入锁

解释一、可重入就是说某个线程已经获得某个锁,可以再次获取锁而不会出现死锁。

解释二、可重入锁又称递归锁,是指同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁(前提是锁对象得是同一个对象),不会因为之前已经获取过锁还没有释放而阻塞。

1.20JVM

java面试总结_第1张图片

1.21、可达性算法

这是目前主流的虚拟机都是采用GC Roots Tracing算法,比如Sun的Hotspot虚拟机便是采用该算法。 该算法的核心算法是从GC Roots对象作为起始点,利用数学中图论知识,图中可达对象便是存活对象,而不可达对象则是需要回收的垃圾内存。这里涉及两个概念,一是GC Roots,一是可达性。

1.22抽象类和接口的区别

1.抽象类中可以有普通成员变量,接口中没有普通成员变量;

2.抽象类可以有构造方法,接口中不能有构造方法;

3.抽象类中可以包含非抽象的普通方法,接口中的所有方法必须都是抽象的,不能有非抽象的普通方法。

4. 抽象类中可以包含静态方法,接口中不能包含静态方法

5. 抽象类中的抽象方法的访问类型可以是 public,protected ,但接口中的抽象方法只能是 public 类型的,并且默认即为 public abstract 类型

7. 抽象类和接口中都可以包含静态成员变量,抽象类中的静态成员变量的访问类型可以任意,但接口中定义的变量只是public static final 类型,并且默认即为 public static final 类型。

d、JDK1.8中对接口增加了新的特性:(1)、默认方法(default method):JDK 1.8允许给接口添加非抽象的方法实现,但必须使用default关键字修饰;定义了default的方法可以不被实现子类所实现,但只能被实现子类的对象调用;如果子类实现了多个接口,并且这些接口包含一样的默认方法,则子类必须重写默认方法;(2)、静态方法(static method):JDK 1.8中允许使用static关键字修饰一个方法,并提供实现,称为接口静态方法。接口静态方法只能通过接口调用(接口名.静态方法名)。

1.23、谈谈你对泛型的理解?

Java 中的泛型有 3 种形式, 泛型方法, 泛型类, 泛型接口。 Java 通过在编译时类型擦除的
方式来实现泛型。擦除时使用 Object 或者界定类型替代泛型, 同时在要调用具体类型方法或
者成员变量的时候插入强转代码,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。

1.24、char 能不能存中文?

可以, 不过, 如果某个特殊的汉字没有被包含在 unicode 编码字符集中, 那么, 这个 char
型变量中就不能存储这个特殊汉字。

1.25、基本数据类型 bit 长度?

  • byte: 1*8
  • short: 2*8
  • int: 4*8
  • long: 8*8
  • float: 4*8
  • double: 8*8
  • char: 2*8
  • boolean: 1*8

1.26、Java 程序是怎样运行的?

首先通过 Javac 编译器将 .java 转为 JVM 可加载的 .class 字节码文件。

1.27、Java6种常见的线程池

FixedThreadPool:核心线程数和最大线程数是一样,线程数固定,当任务数超过线程数,会将任务放队列中等待。

CachedThreadPool:可缓存线程池,可动态增加线程,理论上是可以无限增加。

ScheduledThreadPool:支持定时或周期性执行任务。

SingleThreadExecutor:只有一个线程,发生异常会重新创建线程,所有任务都是按顺序执行。

SingleThreadScheduledExecutor:ScheduledThreadPool和SingleThreadExecutor相结合。

ForkJoinPool :ForkJoinPool 线程池和其他线程池很多地方都是一样的,但重点区别在于它每个线程都有一个自己的双端队列来存储分裂出来的子任务。ForkJoinPool 非常适合用于递归的场景,例如树的遍历、最优路径搜索等场景。

1.28、CPU过高怎么排查

1、top命令:Linux命令。可以查看实时的CPU使用情况。也可以查看最近一段时间的CPU使用情况。

2、PS命令:Linux命令。强大的进程状态监控命令。可以查看进程以及进程中线程的当前CPU使用情况。属于当前状态的采样数据。

3、jstack:Java提供的命令。可以查看某个进程的当前线程栈运行情况。根据这个命令的输出可以定位某个进程的所有线程的当前运行状态、运行代码,以及是否死锁等等。

4、pstack:Linux命令。可以查看某个进程的当前线程栈运行情况。

2、Spring&SpringBoot

2.1、SpringAOP的理解

AOP(Aspect-Oriented Programming:面向切面编程)能够将那些与业务无关, 却为业务模块
所共同调用的逻辑或责任( 例如事务处理、 日志管理、 权限控制等) 封装起来, 便于减少系统
的重复代码, 降低模块间的耦合度, 并有利于未来的可拓展性和可维护性
 

2.2、Spring AOP 底层原理

aop底层采用动态代理的机制实现:

  • 如果要代理的对象实现了某个接口,那么会用JDK代理;
  • 如果没有实现接口,那么会用Cglib代理。由代理类创建出一个和实现类平级的对象(代理对象),它可以实现和原有实现类相同的功能,这个就是aop的横向机制原理,这样就不需要修改源代码。

2.3、Spring Bean 容器的生命周期是什么样的?

  • Bean 容器找到配置文件中 Spring Bean 的定义。
  • Bean 容器利用 Java Reflection API 创建一个 Bean 的实例。
  • 如果涉及到一些属性值 利用 set()方法设置一些属性值。
  • 如果 Bean 实现了 BeanNameAware 接口, 调用 setBeanName()方法, 传入 Bean 的名字。
  • 如果 Bean 在配置文件中的定义包含 init-method 属性, 执行指定的方法。当要销毁 Bean 的时候, 如果 Bean 在配置文件中的定义包含 destroy-method 属性,执行指定的方法。

2.4、Spring 框架事务注解用什么注解? 使用该注解的失效场景

@Transactional
l Transactional 注解应用在非 public 修饰的方法上@Transactional 注解属性
propagation 设置错误
l @Transactional 注解属性 rollbackFor 设置错误
l 同一个类中方法调用, 导致@Transactional 失效
l 异常被 catch“ 吃了” 导致@Transactional 失效

2.5、Spring 框架中用到了哪些设计模式

  • 工厂设计模式 : Spring 使用工厂模式通过 BeanFactory、 ApplicationContext 创建bean 对象。
  • 代理设计模式 : Spring AOP 功能的实现。
  • 单例设计模式 : Spring 中的 Bean 默认都是单例的。
  • 模板方法模式 : Spring 中 jdbcTemplate、 hibernateTemplate 等以 Template 结尾的对数据库操作的类, 它们就使用到了模板模式。
  • 包装器设计模式 : 我们的项目需要连接多个数据库, 而且不同的客户在每次访问中根据需要会去访问不同的数据库。 这种模式让我们可以根据客户的需求能够动态切换不同的数据源
  • 观察者模式: Spring 事件驱动模型就是观察者模式很经典的一个应用
  • 适配器模式 : Spring AOP 的增强或通知(Advice)使用到了适配器模式、 spring MVC 中
  • 也是用到了适配器模式适配 Controller。
     

2.6、注解 @Autowired 和 @Resource 有什么区别

  • Resource 是 JDK 提供的, 而 Autowired 是 Spring 提供的
  • @Autowired 默认按类型装配, 默认情况下必须要求依赖对象存在, 如果要允许 null值, 可以设置它的 required 属性为 false。 如果想使用名称装配可以结合@Qualifier 注解进行使用。
  • @Resource, 默认按照名称进行装配, 名称可以通过 name 属性进行指定, 如果没指定 name 属性, 当注解写在字段上时, 默认取字段名进行名称查找。如果注解写在 setter方法上默认取属性名进行装配。 当找不到与名称匹配的 bean 时才按照类型进行装配。

2.7、springboot和microservice区别

1、SpringBoot只是一个快速开发框架,使用注解简化了xml配置,内置了Servlet容器,以Java应用程序进行执行。

     SpringCloud是一系列框架的集合,可以包含SpringBoot。

2、SpringBoot专注于方便的开发单个个体微服务

      SpringCloud是关注于全局的微服务协调治理框架,它将SpringBoot开发的一个个单体微服务整合并管理起来。

注册中心组件:Eureka、Nacos

负载均衡组件 :Ribbon

远程调用组件 :OpenFeign

网关组件 : Zuul、Gateway

服务保护组件 : Hystrix、Sentinel

服务配置管理组件 : SpringCloudConfig、Nacos

2.8、Sentinel的限流与Gateway的限流有什么差别?

Gateway则采用了基于Redis实现的令牌桶算法。

而Sentinel内部却比较复杂:

  1. 默认限流模式是基于滑动时间窗口算法
  2. 排队等待的限流模式则基于漏桶算法
  3. 而热点参数限流则是基于令牌桶算法

2.9、API应用网关的作用

  • 1、提供统一的入口
  • 2、可以进行权限身份认证等安全管理
  • 3、可以根据流量进行限流
  • 4、数据缓存
  • 5、性能监控

2.10、幂等性解决方案

数据库建立唯一性索引

可以保证最终插入数据库的只有一条数据(比如订单表对订单号进行唯一索引,所有重复提交可能产生同一个订单号的都会被拆除。

token令牌机制
分为两个阶段,获取token和使用token。每次接口请求前先获取一个token,然后再下次请求的时候在请求的header体中加上这个token,后台进行验证,如果验证通过删除token,下次请求再次判断token

先查询后判断

首先通过查询数据库是否存在数据,如果存在证明已经请求过了,直接拒绝该请求,如果没有存在,就证明是第一次进来,直接放行。高并发下不推荐

悲观锁或者乐观锁

悲观锁可以保证每次for update的时候其他sql无法update数据(在数据库引擎是innodb的时候,select的条件必须是唯一索引,防止锁全表)

乐观锁,一般通过version来做乐观锁,这样既能保证执行效率,又能保证幂等。例如: UPDATE tab1 SET col1=1,version=version+1 WHERE version=#version# 

分布式锁

SETNX命令,redisson

3、MySQL

3.1、隔离级别, MySQL 的默认隔离级别是什么?

为了达到事务的四大特性, 数据库定义了 4 种不同的事务隔离级别:

  • READ-UNCOMMITTED( 读取未提交) : 最低的隔离级别, 允许脏读, 也就是可能读取到其他会话中未提交事务修改的数据, 可能会导致脏读、 幻读或不可重复读。
  • READ-COMMITTED( 读取已提交) : 只能读取到已经提交的数据。 Oracle 等多数数据库默认都是该级别 ( 不重复读) , 可以阻止脏读, 但是幻读或不可重复读仍有可能发生。
  •  REPEATABLE-READ( 可重复读) : 对同一字段的多次读取结果都是一致的, 除非数据是被本身事务自己所修改, 可以阻止脏读和不可重复读, 但幻读仍有可能发生。
  •  SERIALIZABLE( 可串行化) : 最高的隔离级别, 完全服从 ACID 的隔离级别。 所有的事务依次逐个执行, 这样事务之间就完全不可能产生干扰, 也就是说, 该级别可以防止脏读、 不可重复读以及幻读。
  •  MySQL 默认采用的 REPEATABLE_READ 隔离级别。
  • 可重复读的核心是在一个事务内多次读取同一个数据,得到的内容都是一致的。

3.2、对 SQL 慢查询会考虑哪些优化 ?

  • 分析语句, 是否加载了不必要的字段/数据
  • 分析 SQL 执行计划( explain extended) , 思考可能的优化点, 是否命中索引等;
  • 查看 SQL 涉及的表结构和索引信息,如果 SQL 很复杂, 优化 SQL 结构;
  • 按照可能的优化点执行表结构变更、 增加索引、 SQL 改写等操作。
  • 查看优化后的执行时间和执行计划。
  • 如果表数据量太大, 考虑分表。
  • 利用缓存, 减少查询次数。

3.3、MySQL索引底层结构为什么使用 B+树?

  • 哈希虽然能够提供 O(1) 的单数据行操作性能, 但是对于范围查询和排序却无法很好地支持, 最终导致全表扫描;
  • B 树能够在非叶节子点中存储数据, 但是这也导致在查询连续数据时可能会带来更多的随机 I/O;
  • 而 B+树的所有叶节点可以通过指针相互连接, 能够减少顺序遍历时产生的额外随机 I/O;
  • B 树一个节点里存的是数据, 而 B+树存储的是索引( 地址) , 所以 B 树里一个节点存不了很多个数据, 但是 B+树一个节点能存很多索引, B+树叶子节点存所有的数据。
  • B+树的高度比B树高低,查询时间复杂度要低;
  • B+树的叶子节点是数据阶段用了一个链表(双向链表)串联起来, 便于范围查找。

3.4、索引失效的情况有哪些

  • like 以%开头索引无效, 当 like 以&结尾, 索引有效;
  • 在索引列上使用 IS NULL 或者 IS NOT NULL 时候, 索引失效, 因为索引是不索引空值得
  • 组合索引, 使用的不是第一列索引时候, 索引失效, 即最左匹配规则
  • 在索引字段上使用, NOT、 <>、 ! = 、 时候是不会使用索引的, 对于这样的处理只会进行全表扫描。
  • or 语句前后没有同事使用索引, 当且仅当 or 语句查询条件的前后列均为索引时, 索引生效。
  • 对索引字段进行计算操作, 函数操作时不会使用索引。
  • 数据类型出现隐式转换, 如 varchar 不加单引号的时候可能会自动转换为 int 类型, 这个时候索引失效。
  • 当全表扫描速度比索引速度快的时候不会使用索引。

3.5、MySQL中有哪几种锁?

全局锁:锁定数据库中的所有表。使用场景是做全库的逻辑备份,对所有的表进行锁定,从而获取一致性视图,保证数据的完整性。

表级锁: 开销小, 加锁快; 不会出现死锁; 锁定粒度大, 发生锁冲突的概率最高, 并发度
最低。
行级锁: 开销大, 加锁慢; 会出现死锁; 锁定粒度最小, 发生锁冲突的概率最低, 并发度
也最高。
页面锁: 开销和加锁时间界于表锁和行锁之间; 会出现死锁; 锁定粒度界于表锁和行锁之
间, 并发度一般

3.6、共享锁和排它锁

共享锁

共享锁(Shared Locks,简称S锁),又称为读锁,同样是一种基本的锁类型。

如果事务T1对数据对象O1加上了共享锁,那么当前事务只能对O1进行读取操作,其他事务也只能对这个数据对象加共享锁——直到该数据对象上的所有共享锁都被释放。

共享锁和排他锁最根本的区别在于,加上排他锁后,数据对象只对一个事务可见,而加上共享锁后,数据对所有事务都可见。

排他锁

排他锁(Exclusive Locks,简称 X 锁),又称为写锁或独占锁,是一种基本的锁类型。如果事务 T1对数据对象 O1加上了排他锁,那么在整个加锁期间,只允许事务 T1对 O1进行读取和更新操作,其他任何事务都不能再对这个数据对象进行任何类型的操作——直到T1释放了排他锁。

从上面讲解的排他锁的基本概念中,我们可以看到,排他锁的核心是如何保证当前有且仅有一个事务获得锁,并且锁被释放后,所有正在等待获取锁的事务都能够被通知到。

3.7、MySQL事务的特性有什么, 说一下分别是什么意思?

  • 原子性: 即不可分割性, 事务要么全部被执行, 要么就全部不被执行。
  • 隔离性:同一时间,只允许一个事务请求同一组数据。不同的事务彼此之间没有干扰。
  • 一致性:事务开始前和结束后,数据库的完整性约束没有被破坏。
  • 持久性:事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失

3.8、MySQL的存储引擎

MyISAM引擎:

1、不支持事务;2、不支持外键,支持表锁,每次所住的是整张表;3、采用非聚集索引;4、被mongodb替代

Innodb引擎:

1、支持事务;2、支持行锁和外键约束;3、主键索引采用聚集索引

Memory引擎:

1、不支持事务;2、表锁;3、Hash索引;4、已被Redis替代

3.9、MySQL 索引分类?

  • 单列索引
    • 普通索引: MySQL 中基本索引类型, 没有什么限制, 允许在定义索引的列中插入重复值和空值, 纯粹为了查询数据更快一点。
    • 唯一索引: 索引列中的值必须是唯一的, 但是允许为空值,
    • 主键索引: 是一种特殊的唯一索引, 不允许有空值
  • 组合索引:
    • 多个字段组合上创建的索引, 只有在查询条件中使用了这些字段的左边字段时, 索引才会被使用, 使用组合索引时遵循最左前缀集合。
  • 全文索引:
    • 只有在 MyISAM 引擎上才能使用, 只能在 CHAR,VARCHAR,TEXT 类型字段上使用全文
    • 索引, 介绍了要求, 说说什么是全文索引, 就是在一堆文字中, 通过其中的某个关键字等, 就能找到该字段所属的记录行, 比如有"你是个靓仔, 靓女 ..." 通过靓仔, 可能就可以找到该条记录
  • 空间索引:

空间索引是对空间数据类型的字段建立的索引, MySQL 中的空间数据类型有四种,GEOMETRY、 POINT、 LINESTRING、 POLYGON。 在创建空间索引时, 使用 SPATIAL 关键字。 要求, 引擎为 MyISAM, 创建空间索引的列, 必须将其声明为 NOT NULL。

3.10Explain语法

id

包含一组数字,表示查询中执行select子句或操作表的顺序,

id相同,执行顺序由上至下

如果是子查询,id的序号会递增,id值越大优先级越高,越先被执行

select_type 

表示查询中每个select子句的类型(简单 OR复杂)

a.SIMPLE:查询中不包含子查询或者UNION

b.PRIMARY:查询中若包含任何复杂的子部分,最外层查询则被标记为:PRIMARY

c.SUBQUERY:在SELECT或WHERE列表中包含了子查询,该子查询被标记为:SUBQUERY

type

表示MySQL在表中找到所需行的方式,又称“访问类型”,常见类型如下:

由左至右,由最差到最好

 ALL:全表扫描,

index:遍历索引扫描,

range:索引范围扫描常见于between、<、>等的查询

ref:非唯一性索引扫描

eq_ref:唯一性索引扫描

key

使用的索引名称,没有则为null

key_len

使用的索引长度

3.11、MySQL 的主从复制了解吗

主库将变更写入 binlog 日志, 然后从库连接到主库之后, 从库有一个 IO 线程, 将主库的
binlog 日志拷贝到自己本地, 写入一个 relay 中继日志中接着从库中有一个 SQL 线程会从中
继日志读取 binlog, 然后执行 binlog 日志中的内容, 也就是在自己本地再次执行一遍 SQL。

主从延迟:
a. 主库的从库太多
b. 从库硬件配置比主库差
c. 慢 SQL 语句过多
d. 主从库之间的网络延迟
e. 主库读写压力大

3.12、索引设计原则

  • 针对于数据量较大,且查询比较频繁的表建立索引
  • 针对于常作为查询条件(where)、排序(order by)、分组(group by)操作的字段建立索引
  • 尽量选择区分度高的列作为索引,尽量建立唯一索引,区分度越高,使用索引的效率越高。
  • 如果是字符串类型的字段,字段的长度较长,可以针对于字段的特点,建立前缀索引。
  • 尽量使用联合索引,减少单列索引,查询时,联合索引很多时候可以覆盖索引,节省存储空间,避免回表,提高查询效率
  • 要控制索引的数量,索引并不是多多益善,索引越多,维护索引结构的代价也就越大,会影响增删改的效率。

3.13、SQL优化

insert:

  • 批量插入数据    Insert into tb_test values(1,'Tom'),(2,'Cat'),(3,'Jerry');
  • 手动控制事务
  • 主键顺序插入,性能要高于乱序插入
  • 如果一次性需要插入大批量数据(比如: 几百万的记录),使用insert语句插入性能较低,此时可以使用MySQL数据库提供的load指令进行插入

order by优化:

  • Using filesort : 通过表的索引或全表扫描,读取满足条件的数据行,然后在排序缓冲区sortbuffer中完成排序操作,所有不是通过索引直接返回排序结果的排序都叫 FileSort 排序。
  • Using index : 通过有序索引顺序扫描直接返回有序数据,这种情况即为 using index,不需要额外排序,操作效率高。

group by优化:

  • A. 在分组操作时,可以通过索引来提高效率。
  • B. 分组操作时,索引的使用也是满足最左前缀法则的

limit 优化:

方案一: 如果 id 是连续的, 可以这样, 返回上次查询的最大记录(偏移量), 再往下 limit
select id, name from employee where id>1000000 limit 10.
方案二: 在业务允许的情况下限制页数:
建议跟业务讨论, 有没有必要查这么后的分页啦。 因为绝大多数用户都不会往后翻太多页。
方案三: order by + 索引( id 为索引)
select id, name from employee order by id limit 1000000, 10
方案四: 利用延迟关联或者子查询优化超多分页场景。 ( 先快速定位需要获取的 id 段, 然后
再关联)
SELECT a.* FROM employee a, (select id from employee where 条件 LIMIT 1000000,10 ) b
where a.id=b.id


count优化:

按照效率排序的话,count(字段) < count(主键 id) < count(1) ≈ count(*),所以尽
量使用 count(*)。
 

update优化:

InnoDB的行锁是针对索引加的锁,不是针对记录加的锁 ,并且该索引不能失效,否则会从行锁
升级为表锁 。
 

3.14、索引条件下推优化

索引条件下推优化(Index Condition Pushdown (ICP) )是MySQL5.6添加的,用于优化数据查询。

  • 不使用索引条件下推优化时存储引擎通过索引检索到数据,然后返回给MySQL服务器,服务器然后判断数据是否符合条件。

  • 当使用索引条件下推优化时,如果存在某些被索引的列的判断条件时,MySQL服务器将这一部分判断条件传递给存储引擎,然后由存储引擎通过判断索引是否符合MySQL服务器传递的条件,只有当索引符合条件时才会将数据检索出来返回给MySQL服务器。索引条件下推优化可以减少存储引擎查询基础表的次数,也可以减少MySQL服务器从存储引擎接收数据的次数。

假设有索引(name, age), 执行 SQL: select * from tuser where name like '张%' and age=10;

java面试总结_第2张图片

MySQL 5.6 以后, 存储引擎根据(name,age)联合索引,找到,由于联合索引中包含列,所以存储引擎直接在联合索引里按照age=10过滤。按照过滤后的数据再一一进行回表扫描。

 java面试总结_第3张图片

索引下推使用条件

  • 只能用于rangerefeq_refref_or_null访问方法;

  • 只能用于InnoDBMyISAM存储引擎及其分区表;

  • 对存储引擎来说,索引下推只适用于二级索引(也叫辅助索引);

索引下推的目的是为了减少回表次数,也就是要减少 IO 操作。对于的聚簇索引来说,数据和索引是在一起的,不存在回表这一说。

  • 引用了子查询的条件不能下推;

  • 引用了存储函数的条件不能下推,因为存储引擎无法调用存储函数。

3.15、show profile性能监控

1、Show Profile默认是关闭的,并且开启后只存活于当前会话,也就说每次使用前都需要开启。也可修改MYSQL配置文件来修改。

2、通过Show Profiles查看sql语句的耗时时间,然后通过Show Profile命令对耗时时间长的sql语句进行诊断。

3、注意Show Profile诊断结果中出现相关字段的含义,判断是否需要优化SQL语句。

4、使用show processlist查看连接的线程个数,来观察是否有大量线程处于不正常的状态或者其他不正常的特征

5、Show Profile的常用查询参数

①ALL:显示所有的开销信息。

②BLOCK IO:显示块IO开销。

③CONTEXT SWITCHES:上下文切换开销。

④CPU:显示CPU开销信息。

⑤IPC:显示发送和接收开销信息。

⑥MEMORY:显示内存开销信息。

⑦PAGE FAULTS:显示页面错误开销信息。

⑧SOURCE:显示和Source_function,Source_file,Source_line相关的开销信息。

⑨SWAPS:显示交换次数开销信息。

3.16、redo/undo log 和 binlog

binlog 用于记录数据库执行的写入性操作(不包括查询)信息,以二进制的形式保存在磁盘中。binlogmysql的逻辑日志,并且由 Server 层进行记录,使用任何存储引擎的 mysql 数据库都会记录 binlog 日志。而redo/undo 是 innodb 引擎层维护的。

redo log 通常是 物理 日志,记录的是 数据页 的物理修改,而不是某一行或某几行修改成怎样怎样,它用来恢复提交后的物理数据页(恢复数据页,且只能恢复到最后一次提交的位置)。Redo Log 保证事务的持久性。

undo log 用来回滚行记录到某个版本。undo log 一般是逻辑日志,根据每行记录进行记录。Undo Log 保证事务的原子性。

4、计算基础知识

4.1、TCP 和 UDP 区别

  • UDP报文头结构简单,需要的资源少,主要用于直播、游戏等对数据实时性的要求比较高的传输;
  • TCP 报文头里面的序号能使 TCP 的数据按序到达
  • 报文头里面的确认序号能保证不丢包,累计确认及超时重传机制
  • TCP 拥有流量控制及拥塞控制的机制
  • TCP 的顺序问题,丢包问题,流量控制都是通过滑动窗口来解决的
    拥塞控制时通过拥塞窗口来解决的
  • TCP 基于连接,有三次握手三次挥手, UDP 基于无连接。
  • TCP 要求系统资源较多, UDP 较少。
  • UDP 程序结构较简单。
  • TCP 保证数据正确性, UDP 可能丢包。
  • TCP 保证数据顺序, UDP 不保证。

4次挥手:

  • TCP 是全双工, 每个方向都必须进行单独关闭。关闭连接时, 当 Server 端收到 关闭连接的
    报文时, 很可能并不会立即关闭 SOCKET, 所以只能先回复一个 ACK 报文, 告诉 Client
    端, ” 你发的 FIN 报文我收到了” 。 只有等到 Server 端所有的报文都发送完了, 我才能发
    送 FIN 报文, 因此不能一起发送。 故需要四步握手
     

4.2、HTTP 与 HTTPS 区别

HTTPS(Hypertext Transfer Protocol Secure:超文本传输安全协议)是一种透过计算机网络进行安全通信的传输协议。HTTPS 经由 HTTP 进行通信,但利用 SSL/TLS 来加密数据包。HTTPS 开发的主要目的,是提供对网站服务器的身份认证,保护交换数据的隐私与完整性。

  • HTTP 明文传输,数据都是未加密的,安全性较差,HTTPS(SSL+HTTP) 数据传输过程是加密的,安全性较好。
  • 使用 HTTPS 协议需要到 CA(Certificate Authority,数字证书认证机构) 申请证书,一般免费证书较少,因而需要一定费用。证书颁发机构如:Symantec、Comodo、GoDaddy 和 GlobalSign 等。
  • HTTP 页面响应速度比 HTTPS 快,主要是因为 HTTP 使用 TCP 三次握手建立连接,客户端和服务器需要交换 3 个包,而 HTTPS除了 TCP 的三个包,还要加上 ssl 握手需要的 9 个包,所以一共是 12 个包。
  • http 和 https 使用的是完全不同的连接方式,用的端口也不一样,前者是 80,后者是 443。
  • HTTPS 其实就是建构在 SSL/TLS 之上的 HTTP 协议,所以,要比较 HTTPS 比 HTTP 要更耗费服务器资源。

4.3、线程 & 进程的区别

  • 线程作为调度和分配的基本单位,进程作为拥有资源的基本单位
  • 一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。
  • 进程拥有自己的内存空间。 线程使用进程的内存空间, 且要和该进程的其他线程共享这个空间; 而不是在进程中给每个线程单独划分一点空间。
  • ( 同一进程中的) 线程在共享内存空间中运行, 而进程在不同的内存空间中运行
  •  线程可以控制同一进程的其他线程。 进程无法控制兄弟进程, 只能控制其子进程
  • 线程可以使用 wait( ) , notify( ) , notifyAll( ) 等方法直接与其他线程( 同一进程)通信; 而, 进程需要使用“ 进程间通信” ( IPC) 来与操作系统中的其他进程通信。
  • 进程之间通过管道(pipe)、信号量(semophore)、套接字(socket)等方式通信

4.4、当你用浏览器打开一个链接的时候, 计算机做了哪些工作步骤?

域名解析– > 发起 TCP 的 3 次握手 – > 建立 TCP 连接后发起 http 请求 – > 服务器响应
http 请求– >浏览器得到 html 代码 – > 浏览器解析 html 代码, 并请求 html 代码中的资
源( 如 js、 css、 图片等) – > 浏览器对页面进行渲染呈现给用户 。

4.5、线程池大小如何设置?

CPU 密集型任务(N+1): 这种任务消耗的主要是 CPU 资源, 可以将线程数设置为 N
( CPU 核心数) +1, 比 CPU 核心数多出来的一个线程是为了防止线程偶发的缺页中断,
或者其它原因导致的任务暂停而带来的影响。 一旦任务暂停, CPU 就会处于空闲状态,
而在这种情况下多出来的一个线程就可以充分利用 CPU 的空闲时间。

I/O 密集型任务(2N): 这种任务应用起来, 系统会用大部分的时间来处理 I/O 交互, 而线
程在处理 I/O 的时间段内不会占用 CPU 来处理, 这时就可以将 CPU 交出给其它线程使
用。 因此在 I/O 密集型任务的应用中, 我们可以多配置一些线程, 具体的计算方法是 2N。

如何判断是 CPU 密集任务还是 IO 密集任务?
CPU 密集型简单理解就是利用 CPU 计算能力的任务比如你在内存中对大量数据进行排序。
单凡涉及到网络读取, 文件读取这类都是 IO 密集型, 这类任务的特点是 CPU 计算耗费时间
相比于等待 IO 操作完成的时间来说很少, 大部分时间都花在了等待 IO 操作完成上。

4.6、线程池主要参数

高并发,大任务时候需要用到多线程。大任务处理起来比较耗时, 这时候可以起到多个线程并行加快处理( 例如: 分片上传) 。 可以提高 CPU 的利用率

  •  corePoolSize : 核心线程数线程数定义了最小可以同时运行的线程数量。
  •  maximumPoolSize : 当队列中存放的任务达到队列容量的时候, 当前可以同时运行的线程数量变为最大线程数。
  • workQueue: 当新任务来的时候会先判断当前运行的线程数量是否达到核心线程数, 如果达到的话, 信任就会被存放在队列中
  • 如果当前线程数
  •  如果当前线程数>=corePoolSize, 则将任务存入 BlockingQueue 。
  • 如果阻塞队列已满, 且当前线程数=maximumPoolSize, 则抛出异常 。

4.7、请描述线程的生命周期, 它们之间如何切换

线程的生命周期包含 5 个阶段, 包括: 新建、 就绪、 运行、 阻塞、 销毁新建( NEW) : 就是刚使用 new 方法, new 出来的线程;
l 就绪( RUNNABLE) : 就是调用的线程的 start()方法后, 这时候线程处于等待 CPU 分
配资源阶段, 谁先抢的 CPU 资源, 谁开始执行;
l 运行( RUNNING) : 当就绪的线程被调度并获得 CPU 资源时, 便进入运行状态, run
方法定义了线程的操作和功能;
l 阻塞( BLOCKED) : 在运行状态的时候, 可能因为某些原因导致运行状态的线程变成了
阻塞状态, 比如 sleep()、 wait()之后线程就处于了阻塞状态, 这个时候需要其他机制将
处于阻塞状态的线程唤醒, 比如调用 notify 或者 notifyAll()方法。 唤醒的线程不会立刻
执行 run 方法, 它们要再次等待 CPU 分配资源进入运行状态;
l Waiting( 无限等待) : 一个线程在等待另一个线程执行一个( 唤醒) 动作时, 该线程进
入 Waiting 状态。 进入这个状态后不能自动唤醒, 必须等待另一个线程调用 notify 方法
或者 notifyAll 方法时才能够被唤醒。
l 销毁( TERMINATED) : 如果线程正常执行完毕后或线程被提前强制性的终止或出现异
常导致结束, 那么线程就要被销毁, 释放资源;
 

4.8、线程池如果满了会怎么样?

        如果使用的是无界队列 Linke dBlockingQueue, 也就是无界队列的话, 没关系, 继续添
加任务到阻塞队列中等待执行, 因为 LinkedBlockingQueue 可以近乎认为是一个无穷大
的队列, 可以无限存放任务

        如果使用的是有界队列比如 ArrayBlockingQueue , 任务首先会被添加到
ArrayBlockingQueue 中, ArrayBlockingQueue 满了, 会根据 maximumPoolSize 的
值增加线程数量, 如果增加了线程数量还是处理不过来, ArrayBlockingQueue 继续满,
那么则会使用拒绝策略 RejectedExecutionHandler 处理满了的任务, 默认是
AbortPolicy。

4.9、什么情况线程会进入 WAITING 状态?

  • 调用 Object 对象的 wait 方法, 但没有指定超时值。
  • 调用 Thread 对象的 join 方法, 但没有指定超时值。
  • 调用 LockSupport 对象的 park 方法

4.10、I/O 多路复用实现方式有哪些

select
poll
epoll
 


4.11、传统 I/O 跟 NIO 的区别?

  • 所有 I/O 都被视为单个的字节的移动, 通过一个称为 Stream 的对象一次移动一个字节。
  • 原来的 I/O 库(在 java.io.*中) 与 NIO 最重要的区别是数据打包和传输的方式。
  • 原来的I/O 以流的方式处理数据, 而 NIO 以块的方式处理数据。
  • NIO 性能的优势就来源于缓冲的机制( buffer 机制) , 不管是读或者写都需要以块的形式写入到缓冲区中。
  • 当我们在执行持续性的操作( 如上传下载) 时, IO 的方式是要优于 NIO 的。 分清情况,
    合理选用。

4.12、IO流


按照读写的单位大小来分:
l 字符流: 以字符为单位, 每次次读入或读出是 16 位数据。 其只能读取字符类型数据。
(Java 代码接收数据为一般为 char 数组, 也可以是别的)
l 字节流: 以字节为单位, 每次次读入或读出是 8 位数据。 可以读任何类型数据, 图片、
文件、 音乐视频等。 (Java 代码接收数据只能为 byte 数组)
按照实际 IO 操作来分:
l 输出流: 从内存读出到文件。 只能进行写操作。
l 输入流: 从文件读入到内存。 只能进行读操作。
l 注意: 输出流可以帮助我们创建文件, 而输入流不会。
按照读写时是否直接与硬盘, 内存等节点连接分:
l 节点流: 直接与数据源相连, 读入或读出。
l 处理流: 也叫包装流, 是对一个对于已存在的流的连接进行封装, 通过所封装的流的功能
调用实现数据读写。 如添加个 Buffering 缓冲区。 ( 意思就是有个缓存区, 等于软件和
mysql 中的 redis)

5、Redis&MQ

Redis的单线程问题

Redis快的主要原因是:

  1. 完全基于内存

  2. 数据结构简单,对数据操作也简单

  3. 使用多路 I/O 复用模型,充分利用CPU资源

单线程优势有下面几点:

  • 代码更清晰,处理逻辑更简单

  • 不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为锁而导致的性能消耗

  • 不存在多进程或者多线程导致的CPU切换,充分利用CPU资源

5.1、谈一谈缓存穿透、 缓存击穿和缓存雪崩, 以及解决办法?

缓存穿透:大量并发查询不存在的 KEY, 在缓存和数据库中都不存在, 同时给缓存和数据库

  • 缓存空值的 KEY, 
  • Bloom 过滤或 RoaingBitmap 判断 KEY 是否存在

缓存击穿:某个 KEY 失效的时候, 正好有大量并发请求访问这个 KEY

  • 缓存击穿主要担心的是某个Key过期,更新缓存时引起对数据库的突发高并发访问。
  • 因此我们可以在更新缓存时采用互斥锁控制,只允许一个线程去更新缓存,其它线程等待并重新读取缓存。
  • 例如Redis的setnx命令就能实现互斥效果。

缓存雪崩:当某一时刻发生大规模的缓存失效的情况, 导致大量的请求无法获取数据, 从而将流量压力传导到数据库上, 导致数据库压力过大甚至宕机。

  • 不同分类数据,缓存不同时间周期
  • 相同分类数据,采用定时长加随机数方式
  • 热点数据缓存时间长一些,冷门数据缓存时间短些
  • 搭建主从集群,保证高可用

5.2、Redis9种基本数据结构以及8种内部编码

java面试总结_第4张图片

string:最基本的数据类型,二进制安全的字符串,最大512M。

应用:

缓存,热点数据

分布式session

分布式锁

文章的阅读量,微博点赞数,允许一定的延迟,先写入 Redis 再定时同步到数据库

全局ID

内部编码:

int:8 个字节的长整型(long,2^63-1)

embstr:小于等于44个字节的字符串,embstr格式的SDS(Simple Dynamic String)

raw:SDS大于 44 个字节的字符串

list:按照添加顺序保持顺序的字符串列表(双向列表)。

应用:

关注列表、粉丝列表

消息队列

内部编码:

zipList

linkList

set:无序的字符串集合,不存在重复的元素。set 对外提供的功能与 list 类似是一个列表的功能,特殊之处在于 set 是可以自动排重的。

应用:

知乎点赞

共同关注人、可能认识的人(多个 set 取交集、并集、差集)

商城商品筛选

内部编码:

intset(整数集合):当集合中的元素都是整数,并且集合中的元素个数小于 512 个时,Redis 会选用 intset 作为底层内部实现。

hashtable(哈希表):当上述条件不满足时,Redis 会采用 hashtable 作为底层实现。

sorted set:已排序的字符串集合。

java面试总结_第5张图片

内部编码: 

ziplist(压缩列表)

skiplist(跳跃表)
 

hash:key-value对的一种集合。

应用:

对象数据,用户 ID 为查找的 key,存储的 value 用户对象包含姓名,年龄,生日等信息。

购物车

内部编码:

ziplist(压缩列表):当哈希类型中元素个数小于 hash-max-ziplist-entries 配置(默认 512 个),同时所有值都小于 hash-max-ziplist-value 配置(默认 64 字节)时,Redis 会使用 ziplist 作为哈希的内部实现。

hashtable(哈希表):当上述条件不满足时,Redis 则会采用 hashtable 作为哈希的内部实现。
 

bitmap:更细化的一种操作,以bit为单位。有一些数据只有两个属性,比如是否是学生,是否是党员等等,对于这些数据,最节约内存的方式就是用bit去记录,以是否是学生为例,1代表是学生,0代表不是学生

应用:

在线状态

签到状态

hyperloglog:基于概率的数据结构,Redis 的基数统计,这个结构可以非常省内存的去统计各种计数。 它在 Redis 的内部结构表现就是一个字符串位图。你可以把 HyperLogLog 对象当成普通的字符串来进行处理。# 2.8.9新增

应用

注册 IP 数、每日访问 IP 数、页面实时UV)、在线用户数

Geo:地理位置信息储存起来, 并对这些信息进行操作   # 3.2新增

内部编码:

Geo本身不是一种数据结构,它本质上还是借助于Sorted Set

应用场景:

比如现在比较火的直播业务,我们需要检索附近的主播,那么GEO就可以很好的实现这个功能。

一是主播开播的时候写入主播Id的经纬度,

二是主播关播的时候删除主播Id元素,这样就维护了一个具有位置信息的在线主播集合提供给线上检索。

流(Stream):用一句话概括Streams就是Redis实现的内存版kafka。支持多播的可持久化的消息队列,用于实现发布订阅功能,借鉴了 kafka 的设计。# 5.0新增

内部编码:

streams底层的数据结构是radix tree:Radix Tree(基数树) 事实上就几乎相同是传统的二叉树 

5.3、Redis 主从同步是怎么实现的

主从刚刚连接的时候, 进行全量同步; 全同步结束后, 进行增量同步。 当然, 如果有需要,
slave 在任何时候都可以发起全量同步。 redis 策略是, 无论如何, 首先会尝试进行增量同步,
如不成功, 要求从机进行全量同步。
 

5.4、Redis 持久化 RDB 和 AOF 优缺点

RDB:

RDB 持久化方式, 是将 Redis 某一时刻的数据持久化到磁盘中, 是一种快照式的持久化方

优点:

RDB 作为一个非常紧凑( 有压缩) 的文件, 可以很方便传送到另一个远端数据中心 , 非常适用于灾难恢复。

RDB 在保存 RDB 文件时父进程唯一需要做的就是 fork 出一个子进程,接下来的工作全部由子进程来做, 父进程不需要再做其他 IO 操作, 所以 RDB 持久化方式可以最大化redis 的性能。

与 AOF 相比, 在恢复大的数据集的时候, RDB 方式会更快一些

缺点:

Redis 意外宕机时, 会丢失部分数据。

当 Redis 数据量比较大时, fork 的过程是非常耗时的, fork 子进程时是会阻塞的, 在这期间 Redis 是不能响应客户端的请求的

AOF:

AOF 方式是将执行过的写指令记录下来, 在数据恢复时按照从前到后的顺序再将指令都执行
一遍。
优点:

持久化效率更高,AOF 文件可读性高, 分析容易

缺点:

对于相同的数据来说, AOF 文件大小通常要大于 RDB 文件。

AOF恢复数据的速度可能会慢于 RDB。

5.5、分布式锁

基于数据库实现分布式锁

基于缓存实现分布式锁

基于 Zookeeper 实现分布式锁

Redis 分布式锁实现: 先拿 setnx 来争抢锁, 抢到之后, 再用 expire(过期)给锁加一个
过期时间防止锁忘记了释放。setnx 和 expire 合成一条指令来用的!

5.6、如何保证 Redis 中的数据不丢失?

使用 AOF 和 RDB 结合的方式
RDB 做镜像全量持久化, AOF 做增量持久化。 因为 RDB 会耗费较长时间, 不够实时, 在
停机的时候会导致大量丢失数据, 所以需要 AOF 来配合使用
Redis 集群模式
master 节点持久化
Redis 断点续传
从 redis 2.8 开始, 就支持主从复制的断点续传, 如果主从复制过程中, 网络连接断掉了,
那么可以接着上次复制的地方, 继续复制下去, 而不是从头开始复制一份。
主备切换的过程, 可能会导致数据丢失
解决异步复制和脑裂导致的数据丢失
redis.conf 中
min-slaves-to-write 1
min-slaves-max-lag 10
要求至少有 1 个 slave, 数据复制和同步的延迟不能超过 10 秒
如果说一旦所有的 slave, 数据复制和同步的延迟都超过了 10 秒钟, 那么这个时候,
master 就不会再接收任何请求了
上面两个配置可以减少异步复制和脑裂导致的数据丢失。

5.7Redis的Key过期策略

过期删除策略

1)定时删除:在设置某个key 的过期时间同时,我们创建一个定时器,让定时器在该过期时间到来时,立即执行对其进行删除的操作。占用大量的CPU资源去处理过期的数据,影响Redis的吞吐量和响应时间

2)惰性删除:当访问一个key时,才判断是否过期,过期则删除。可以节省CPU资源,但是占用内存较大。

3)定期删除:隔一段时间扫描过期的key。折中的方案

在Redis中,同时使用了定期删除和惰性删除,不能保证过期key100%被删除。

内存淘汰策略

Redis 内存数据集大小上升到一定大小的时候, 就会施行数据淘汰策略。 Redis 提供 6 种
数据淘汰策略:

  1. volatile-lru: 从已设置过期时间的数据集( server.db[i].expires) 中挑选最近最少使用的数据淘汰
  2.  volatile-ttl: 从已设置过期时间的数据集( server.db[i].expires) 中挑选将要过期的数据淘汰
  3.  volatile-random: 从已设置过期时间的数据集( server.db[i].expires) 中任意选择数据淘汰
  4.  allkeys-lru: 从数据集( server.db[i].dict) 中挑选最近最少使用的数据淘汰
  5.  allkeys-random: 从数据集( server.db[i].dict) 中任意选择数据淘汰
  6.  no-enviction( 驱逐) : 禁止驱逐数据
  7. 5.8、如何实现数据库与缓存数据一致

本地缓存同步:当前微服务的数据库数据与缓存数据同步,可以直接在数据库修改时加入对Redis的修改逻辑,保证一致。

跨服务缓存同步:服务A调用了服务B,并对查询结果缓存。服务B数据库修改,可以通过MQ通知服务A,服务A修改Redis缓存数据

通用方案:使用Canal框架,伪装成MySQL的salve节点,监听MySQL的binLog变化,然后修改Redis缓存数据

5.9、RabbitMQ 如何保证消息不丢失

生产者: 开启 confirm 模式(异步, 性能较好),(1)确认消息已经到达交换机(2)确认消息到达队列

MQ: (1)exchange 持久化 (2)queue 持久化 (3)消息持久化

消费者: (1)消息确认,关闭自动 应答(ACK),使用手动应答,(2)消息重试机制,(3)消息多次失败,可投递给异常交换机。

5.10、如何防止MQ消息被重复消费

给每一条消息都添加一个唯一id,在本地记录消息表及消息状态,处理消息时基于数据库的id唯一性做判断;

基于业务本身的幂等。比如根据id的删除、查询业务天生幂等;新增、修改等业务可以考虑基于数据库id唯一性、或乐观锁机制确保幂等。

5.11、RabbitMq的五种工作模式

1、简单模式

一个队列只被一个消费者监听消费

发短信、邮件

2、争抢模式

多个消费者同时绑定多个队列,形成争抢消息的效果

抢红包

3、路由模式

消息携带routing key和队列绑定的routing key,如果匹配上,就把消息发送给队列

4、发布订阅

一个消息通过交换机发送给多个队列

群发短信

5、主题模式topic

和路由模式类似,区别是可以使用通配符绑定队列

6、微服务

6.1、认识微服务

单体架构:所有的业务集中在一个项目中开发,打成一个包部署

        架构简单、部署成本低;耦合度高、维护困难、升级困难

分布式架构:根据业务功能对系统做拆分,每个业务功能模块作为独立项目开发,称作一个服务,但是所有服务都连接一个数据库。

        降低服务耦合、有利于服务升级和拓展;调用关系错综复杂,数据库压力依然存在

微服务架构:每个服务对应唯一的业务能力和独立的数据库,做到单一职责;

        数据独立、独立部署和交付;提供统一的标准接口,与语言和技术无关;服务之间隔离性强;做到了高内聚,低耦合。

6.2、SpringCloudAlibaba常见组件

注册中心组件:Eureka、Nacos

负载均衡组件:Ribbon

远程调用组件:OpenFeign

网关组件:Zuul、Gateway

服务保护组件:Hystrix、Sentinel

服务配置管理组件:SpringCloudConfig、Nacos

6.3、Nacos的服务注册表结构

Nacos采用了数据的分级存储模型,最外层是Namespace,用来隔离环境。然后是Group,用来对服务分组。接下来就是服务(service),一个服务包含多个实例,但是可能处于不同机房,因此service下有多个集群(Cluster),Cluster下是不同的实例(Instance) 

java面试总结_第6张图片

 6.4、Nacos如何支持高并发的

Nacos内部接收到注册请求时,不会立即写数据,而是将服务注册的任务放入一个阻塞队列就立即响应给客户端。然后利用线程池读取阻塞队列中的任务,异步来完成实例更新,从而提高并发读写能力。

Nocas在更新实例列表时,会采用CopyOnWrite技术,首先将旧的实例列表拷贝一份,然后更新拷贝的实例列表,再用更新后的实例列表来覆盖旧的实例列表。

这样就避免了并发读写冲突问题,也不会出现脏读问题。

6.5、Nacos与Eureka的区别有哪些

接口方式:Nacos与Eureka都对外暴露了Rest风格的API接口,用来实现服务注册、发现等功能。

实例类型:Nacos的实例有永久实例和临时实列之分;而Eureka只支持临时实列。

健康检测:Nacos对临时实例采用心跳模式检测(5秒),对永久实例采用主动请求来检测;Eureka只支持心跳模式(默认30秒 )。

服务发现:Nacos支持定时拉取和订阅推送两种模式;Eureka只支持定时拉取模式。

CAP:Nacos集群默认采用AP方式,当集群中存在非临时实例时,采用CP模式;Eureka采用AP方式

java面试总结_第7张图片

6.6、Sentinel的线程隔离与Hystix的线程隔离有什么差别

Hystix默认是基于线程池实现的线程隔离,每一个被隔离的业务都要创建一个独立的线程池,线程过多会带来额外的CPU开销,性能一般,但是隔离性更强。

Sentinel是基于信号量实现的线程隔离,不用创建线程池,性能较好,但是隔离性一般。

6.8、Sentinel限流策略

限流是对服务的保护,避免因瞬间高并发流量而导致服务故障,进而避免雪崩,是一种预防措施。

超时处理,线程隔离,降级熔断是在部分服务故障时,将故障控制在一定范围,避免雪崩,是一种补救措施。

流控模式:

java面试总结_第8张图片

1、直接模式:统计当前资源的请求,触发阈值时对当前资源直接限流,默认的模式,基于滑动时间窗口算法

2、关联模式:统计当前资源管理的另一个资源,触发阈值时,对当前资源限流。

3、链路模式:统计从指定链路访问到本资源的请求,触发阈值时,对指定链路限流。

流控效果:

1、快速失败:QPS超过阈值时,拒绝新的请求,默认的处理方式。

2、warm up: 预热模式,QPS超过阈值时,拒绝新的请求;QPS阈值是逐渐提升的,可以避免冷启动时高并发导致服务宕机。

3、排队等待:请求会进入队列,按照阈值允许的时间间隔依次执行请求;如果请求预期等待时长大于超时时间,直接拒绝(基于漏桶算法

热点参数限流(基于令牌桶算法):

之前的限流是统计访问某个资源的所有请求,判断是否超过QPS阈值。而热点参数限流是分别统计参数值相同的请求,判断是否超过QPS阈值。

6.9、三种常见的限流算法

1、令牌桶算法

java面试总结_第9张图片

实现思路:可以准备一个队列,用来保存令牌,另外通过一个线程池定期生成令牌放到队列中,每来一个请求,就从队列中获取一个令牌,并继续执行。

2、漏桶算法

漏桶算法其实很简单,可以粗略的认为就是注水漏水过程,往桶中以一定速率流出水,以任意速率流入水,当水超过桶流量则丢弃,因为桶容量是不变的,保证了整体的速率。

java面试总结_第10张图片

在算法实现方面,可以准备一个队列,用来保存请求,另外通过一个线程池定期从队列中获取请求并执行,可以一次性获取多个并发执行。

3、滑动窗口算法

计数器算法是限流算法里最简单也是最容易实现的一种算法。比如我们规定,对于A接口来说,我们1分钟的访问次数不能超过100个。那么我们可以这么做:在一开 始的时候,我们可以设置一个计数器counter,每当一个请求过来的时候,counter就加1,如果counter的值大于100并且该请求与第一个 请求的间隔时间还在1分钟之内,那么说明请求数过多;如果该请求与第一个请求的间隔时间大于1分钟,且counter的值还在限流范围内,那么就重置 counter,具体算法的示意图如下:

6.10、云原生

java面试总结_第11张图片

微服务

每个功能都被称为一个独立的服务,可以单独构建和部署,其中某个服务出现故障也不会影响其他的功能模块。解决低耦合+高内聚的问题

DevOps

早期的项目使用的是“瀑布模型”进行软件交付,即一个阶段所有的工作完成后再往下一个阶段,但这样的模式无法满足业务快速开发交付和变更需求的情况。于是后面就出现了敏捷开发这一概念。即一种快速应对需求变化软件开发能力,而DevOps就是基于敏捷开发将软件开发、测试人员、IT运维关联在一起,通过工具、组织等方式使开发、测试、发布流程自动化、软件发布频繁、高效。

容器化

 容器化的好处在于运维的时候不需要再关心每个服务所使用的技术栈了,每个服务都被无差别的封装在容器里,可以被无差别的管理和维护。

6.11、分布式事务框架Seata

全局事务ID Transaction ID ——XID

TC-事务的协调者(Seata服务器)

维护全局和分支事务的状态,驱动全局事务提交或回滚。

TM-事务管理器(@GlobalTransactional)

定义全局事务的范围,开始全局事务、提交或回滚全局事务

RM-资源管理器(事务的参与方)

管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。

分布式事务执行流程:

  1. TM开启分布式事务(TM向TC注册全局事务记录)
  2. 按业务场景,编排数据库、服务等事务内资源(RM向TC汇报资源准备状态)
  3. TM结束分布式事务,事务一阶段结束(TM通知TC提交、回滚分布式事务)
  4. TC汇总事务信息,决定分布式事务是提交还是回滚
  5. TC通知所有RM提交、回滚资源,事务二阶段结束

Seata提供了四种不同的分布式事务解决方案:

  • XA模式:强一致性分阶段事务模式,牺牲了一定的可用性,无业务侵入
  • TCC模式:最终一致的分阶段事务模式,有业务侵入
  • AT模式:最终一致的分阶段事务模式,无业务侵入,也是Seata的默认模式
  • SAGA模式:长事务模式,有业务侵入

java面试总结_第12张图片

6.12、Nacos一致性协议

对于非临时数据,Nacos采用的是Raft协议,而临时数据Nacos采用的是Distro协议。

Raft协议是一种强一致性、去中心化、高可用的分布式协议,它是用来解决分布式一致性问题的。

许多中间件都是利用Raft协议来保证分布式一致性的,例如Redis的sentinel,CP模式的Nacos的leader选举都是通过Raft协议来实现的。因为Nacos的一致性协议是采用的Raft协议。

如果注册Nacos的client节点注册时ephemeral=true,那么Nacos集群对这个client节点的效果就是AP,采用distro协议实现;而注册Nacos的client节点注册时ephemeral=false,那么Nacos集群对这个节点的效果就是CP的,采用raft协议实现。根据client注册时的属性,AP,CP同时混合存在,只是对不同的client节点效果不同。Nacos可以很好的解决不同场景的业务需求。

6.13、Nacos为什么要设计两种模式

对于临时实例,健康检查失败,则直接可以从列表中删除。这种特性就比较适合那些需要应对流量突增的场景,服务可以进行弹性扩容。当流量过去之后,服务停掉即可自动注销了。

对于持久化实例,健康检查失败,会被标记成不健康状态。它的好处是运维可以实时看到实例的健康状态,便于后续的警告、扩容等一些列措施。

6.14、Nacos的保护阈值

Nacos中可以针对具体的实例设置一个保护阈值,值为0-1之间的浮点类型。本质上,保护阈值是⼀个⽐例值(当前服务健康实例数/当前服务总实例数)。

⼀般情况下,服务消费者要从Nacos获取可⽤实例有健康/不健康状态之分。Nacos在返回实例时,只会返回健康实例。

但在⾼并发、⼤流量场景会存在⼀定的问题。比如,服务A有100个实例,98个实例都处于不健康状态,如果Nacos只返回这两个健康实例的话。流量洪峰的到来可能会直接打垮这两个服务,进一步产生雪崩效应。

保护阈值存在的意义在于当服务A健康实例数/总实例数 < 保护阈值时,说明健康的实例不多了,保护阈值会被触发(状态true)。

Nacos会把该服务所有的实例信息(健康的+不健康的)全部提供给消费者,消费者可能访问到不健康的实例,请求失败,但这样也⽐造成雪崩要好。牺牲了⼀些请求,保证了整个系统的可⽤。

这里我们看到了不健康实例的另外一个作用:防止产生雪崩。

那么,如果所有的实例都是临时实例,当雪崩场景发生时,Nacos的阈值保护机制是不是就没有足够的(包含不健康实例)实例返回了?如果有一部分实例是持久化实例,即便它们已经挂掉,状态为不健康的,但当触发阈值保护时,还是可以起到分流的作用。

6.15、Nacos 哪些地方用到了 AP 和 CP?

  • 针对临时服务实例,采用 AP 来保证注册中心的可用性,Distro 协议。

  • 针对持久化服务实例,采用 CP 来保证各个节点的强一致性,JRaft 协议。(JRaft 是 Nacos 对 Raft 的一种改造)

  • 针对配置中心,无 Database 作为存储的情况下,Nacos 节点之间的内存数据为了保持一致,采用 CP。Nacos 提供这种模式只是为了方便用户本机运行,降低对存储依赖,生产环境一般都是通过外置存储组件来保证数据一致性。

  • 针对配置中心,有 Database 作为存储的情况下,Nacos 通过持久化后通知其他节点到数据库拉取数据来保证数据一致性,另外采用读写分离架构来保证高可用,所以这里我认为这里采用的 AP

7、机器学习

7.1、机器学习流程

java面试总结_第13张图片

7.2、机器学习算法

监督学习:是指每个进入算法的训练样本数据都有对应的目标值。

如上图2所示,Ifhealth为目标值。

常见的监督学习算法:

java面试总结_第14张图片

无监督学习:就是训练样本的数据里没有目标列,不依赖于打标好的机器学习算法。

那么,这样的数据可能对一些分类和回归的场景就不太适合了。

无监督学习主要是来解决一些聚类场景的问题。

java面试总结_第15张图片

半监督学习:训练数据里只有部分数据是打标的。目前,半监督学习的算法,都是监督学习算法的变形。

强化学习:强化学习是一种比较复杂的机器学习种类。强调的是:系统与外界不断的交换,获得外界的反馈,然后决定自身的行为。

如:无人驾驶,阿尔法狗下围棋就是强化学习的应用。

你可能感兴趣的:(java,java,数据结构,开发语言)