Java 基础

java7 和 java8差异

Java 泛型

泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。
泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。

泛型方法

写一个泛型方法,该方法在调用时可以接收不同类型的参数。根据传递给泛型方法的参数类型,编译器适当地处理每一个方法调用。

泛型类

泛型类的声明和非泛型类的声明类似,除了在类名后面添加了类型参数声明部分。
和泛型方法一样,泛型类的类型参数声明部分也包含一个或多个类型参数,参数间用逗号隔开。一个泛型参数,也被称为一个类型变量,是用于指定一个泛型类型名称的标识符。因为他们接受一个或多个参数,这些类被称为参数化的类或参数化的类型。

类型通配符

类型通配符一般是使用?代替具体的类型参数。例如 List<?> 在逻辑上是List,List 等所有List<具体类型实参>的父类。
类型通配符上限通过形如List来定义,如此定义就是通配符泛型值接受Number及其下层子类类型。

public static void getUperNumber(List<? extends Number> data) { System.out.println(“data :” + data.get(0)); }

String 能否被继承

因为Sting是这样定义的:public final class String extends Object,里边有final关键字,所以不能被继承。

什么样的类不能被继承?

 一,在Java中,只要是被定义为final的类,也可以说是被final修饰的类,就是不能被继承的。
 二,final是java中的一个关键字,可以用来修饰变量、方法和类。用关键词final修饰的域成为最终域。用关键词final修饰的变量一旦赋值,就不能改变,也称为修饰的标识为常量。如果一个类的域被关键字final所修饰,它的取值在程序的整个执行过程中将不会改变。
 三,假如说整个类都是final,就表明自己不希望从这个类继承,或者不答应其他任何人采取这种操作。换言之,出于这样或那样的原因,我们的类肯定不需要进行任何改变;或者出于安全方面的理由,我们不希望进行子类化(子类处理)。

抽象类和接口之间的区别?

概念不同,抽象类是对最基础类的抽象,接口是对动作方法的抽象。
相同点
1)两者都不能实例化,抽象类必须指向实现抽象方法的子类对象 接口必须指向实现所有接口的类对象
2)接口只能做方法声明,抽象类可以声明方法,也可以做方法实现
3)抽象类可以有具体的方法和属性,接口只能有抽象方法和不可变常量

异常

编译时异常 和 运行时异常 父类都是Throwable

Error
Java运行时系统的内部错误和资源耗尽错误。应用不会抛出该类对象,如果出现了这样的错误,除了告知用户,剩下的就是尽力是程序安全的终止。

Exception

CheckedException: IO异常 SQL 异常
发生在编译阶段,Java编译器会强制程序捕获相关异常 try catch finally

RunTimeException: 空指针 、类型转换异常
在Java虚拟机运行期间可能出现的异常,一定是代码的问题。

异常处理的三种方式:try catch ; throw throws

Compator compare 方法

compartor 结果
compare(第二个元素 第一个元素)

返回-1 交换元素
返回0 相同元素不排序
返回1 不排序

X>Y 返回-1 第二个元素比第一个元素大,交换位置,从大到小排列 降序排列
X<Y 返回-1 第二个元素比第一个 元素小,交换位置,从小到大排列 升序排列

面向对象特征

抽象 :抽象是将一类对象的共同特征总结出来构造类的过程,包括数据抽象和行为抽象两方面。抽象只关注对象有哪些属性和行为,并不关注这些行为的细节是什么
继承:继承是从已有类得到继承信息创建新类的过程
封装:通常认为封装是把数据和操作数据的方法绑定起来 对数据的访问只能通过已定义的接口。
多态:多态性是指允许不同子类型的对象对同一消息作出不同的响应。简单的说就是用同样的对象引用调用同样的方法但是做了不同的事情。多态性分为编译时的多态性和运行时的多态性。
重载(overload)实现的是编译时的多态性(也称为前绑定),
重写(override)实现的是运行时的多态性(也称为后绑定)
重载发生在一个类中,同名的方法如果有不同的参数列表(参数类型不同、参数个数不同或者二者都不同)则视为重载;
重写发生在子类与父类之间,重写要求子类被重写方法与父类被重写方法有相同的返回类型,比父类被重写方法更好访问,不能比父类被重写方法声明更多的异常(里氏代换原则)

  • 访问修饰符 public private protected 以及不写时的区别

  • 基本类型和引用类型

联名 ArrayList

集合

ArrayList
容量不够 *1.5+1内部通过数组实现 中间位置插入或者删除元素,需要对数据进行复制和移动,代价较高,适合随机查找和遍历

  • for 循环遍历删除*

单线程:
正向循环:会遗留连续重复的元素
调用list.remove(i) 之后,List 大小变化,如果相等元素相邻,会造成后者不能删除。因为后者的下标在删除第一个元素时候,已经变化。元素如果不相邻,则删除没有问题。

逆向循环:没有问题

多线程:反向遍历删除没有问题

使用Iterator 循环删除:
list.remove(iterator.next())
list 分为根据下标删除和删除对象

用Iterator循环删除的时候,调用的是ArrayList里面的remove方法,删除元素后modCount会增加,expectedModCount则不变,这样就造成了expectedModCount != modCount,那么就抛出异常了

Vector
线程同步

LinkedList
动态数据插入和删除,随机访问和遍历慢。可操作性表头表尾

HashSet
排列无序,不可重复。底层用hash实现 内部是hashMap

TreeSet
排列无序,不可重复,底层二叉树实现

LinkedHashSet
采用hash表存储 并用双向链表记录插入顺序
内部是LinkedHashMap

Queue
两端出入的列表,可以使用数据或链表实现

HashMap
键不可重复,值可重复
底层Hash
允许key值为null,value也可以为null

Java7 与Java8实现差异:Java 7 数组+单向链表 Java8 数组+链表+红黑树(当链表元素超过8个以后,使用红黑树)

CurrentHashMap

Hashtable
key value 都不允许为空

TreeMap
实现了SortedMap接口,可以根据键值的升序排序

JavaIO NIO

  • 阻塞IO

当用户线程发出IO请求后,内核会去查看数据是否就绪,如果没有就绪会等待数据就绪,用户线程处于阻塞状态,用户线程交出COU。当数据就绪后,内核会将数据拷贝到用户线程,并返回结果给用户线程,用户线程解除block状态。

  • 非阻塞IO

用户线程发起read操作后,不需要等待,马上得到结果,如果结果是error, 说明数据未准备好。会再次发送read操作,一旦内核数据准备好,并且再次收到用户线程请求,内核就把数据copy到用户线程,然后返回。
在非阻塞IO模型中,用户线程需要不断询问内核数据是否就绪,会一直占用CPU,造成CUO占用率高。很少用while循环方式读取数据

  • 多路复用IO

比如Java NIO, 在多路复用IO模型中,会有一个线程不断去轮询多个socket状态,只有当socket真正有读写时间时,才真正调用实际的IO读写操作。该模型减少了资源占用。
JAVA NIO 通过selector.select 去查询每个通道是都有到达事件,如果没有事件,就会一直阻塞在那里。导致用户线程阻塞、 适用于连接数多的情况。

  • 信号驱动IO模型

  • 异步IO模型

理想IO模型。

IO包 JAVA NIO
三大核心部分:Channel (通道) Buffer(缓冲区) Selector 。传统IO基于字节流和字符流进行操作,而NIO基于Channel和Buffer 进行操作,数据总是从通道读取到缓冲区,或者从缓冲区写入到通道中。

image.png**

IO 面向流 NIO 面向缓冲区

Java 反射

概念:反射只在运行状态中知道类所有的属性和方法,并且对于任意一个对象,都能够调用它的任意一个方法

Java 程序中的许多对象在运行时都会出现两种类型:编译时类型和与运行时类型。编译时类型由声明对象时实际用的类型决定,运行时的类型有实际赋值给对象的类型决定。
如果编译时根本无法预知该对象和类属于那些类,程序是能依靠运行时信息来发现该对象和类的真实信息,此时使用反射。
核心类:
Class 获取类的属性、方法等信息
Field 表示类的成员变量,可以用来获取和设置类之中的属性值
Method 表示类的方法,可以用来获取类中的方法信息或者执行方法
Contructor 表示类的构造方法

java信号量

Java 复制

JAVA 引用

  • 强引用
  • 软引用
  • 弱引用
  • 虚引用


HashMap Hashtable 区别 CurrentHashMap

HashMap 基于哈希表实现,每个元素是一个Key-Value 对 内部通过单链表解决冲突,容量不足时,会自动增长
HashMap 是非线程安全,只能用于单线程环境,多线程可以使用concurent包下面的concurrentHashMap
HashMao实现了Serializable接口,支持序列化,实现了Cloneable 接口,能被克隆
HashMap 中的key和value 都允许为null, key为null 的键值永远都放在table[0]为头结点的链表中

1)继承父类不同:Hashtable 继承自Dictionary 类 HashMap 继承自AbstractMap类,两者都实现了Map接口

1)继承父类不同:Hashtable 继承自Dictionary 类 HashMap 继承自AbstractMap类,两者都实现了Map接口
2)线程安全性不同:Hashtable 中的方法都是synchronize的,线程安全;
3)是否提供contains 方法: HashMap 去掉contains方法,改成了containsValue 和 containsKey 方法;Hashtable 保留了contains方法,同时有containsValue 和containsKey containsValue 同contains方法
4)key 和value 是否允许为空:HashMap 允许key和value 为null ,containsValue 和 containsKey 方法判断包含对应键值对;Hashtable 键值对都不能为null
5)计算hash不同: HashMap 通过key计算的hashCode 计算 hash之后,又通过右移16位后相异或,得到新的hash值; Hashtable 直接使用hashCode 作为最终的hash值
6)扩容方式不同:HashMap 默认初始大小为16 容量必须是2的整数次幂,扩容时将容量变为原来的2倍; Hashtable 默认初始大小为11,扩容时变为原来的2倍加1
7)解决hash冲突方式不同

CurrentHashMap 线程安全

是ConcurrentHashMap的工作机制,通过把整个Map分为N个Segment(类似HashTable),可以提供相同的线程安全,但是效率提升N倍,默认提升16倍。Segment 继承 ReentrantLock

Spring MVC

spring MVC运行原理?

  1. 客户端请求提交到DispatchServlet
    2. 由DispatcherServlet 控制器查询一个或多个HandlerMapping ,找到请求的Controller

  2. DispatcherServler 将请求提交到Controller

  3. Controller 调用业务逻辑处理后,返回ModelAndView
  4. DisatcherSerlvet 查询一个或多个ViewResoler 视图解析器,找到ModelAndView 指定视图
  5. 视图负责将结果显示到客户端

注入实现

  • 构造器注入
  • setter注入
  • 静态工厂
  • 实例工厂

使用构造函数依赖注入时,Spring保证所有一个对象所有依赖的对象先实例化后,才实例化这个对象。(没有他们就没有我原则)
使用set方法依赖注入时,Spring首先实例化对象,然后才实例化所有依赖的对象。

Spring框架中的核心思想包括什么

主要是IOC反转控制、DI 依赖注入 AOP 面向切片编程

IOC ( Inversion of Control) 反转控制 :
实现将组件间的关系从程序内部,提取到外部容器(spring xml)
外部容器xml 会动态的注册业务所需的对象(接口 类)
Spring 会提供 IoC 容器来管理和容纳我们所开发的各种各样的 Bean,并且我们可以从中获取各种发布在 Spring IoC 容器里的 Bean,并且通过描述可以得到它。
IoC 是如何实现的
最后我们简单说说IoC是如何实现的。想象一下如果我们自己来实现这个依赖注入的功能,我们怎么来做? 无外乎:

读取标注或者配置文件,看看JuiceMaker依赖的是哪个Source,拿到类名
使用反射的API,基于类名实例化对应的对象实例
将对象实例,通过构造函数或者 setter,传递给 JuiceMaker

依赖注入 构造器 推荐 set get 方法 强依赖 弱依赖

DI 依赖注入:
组件之间的依赖关系由容器在应用系统运行期来决定,也就是容器动态的将某种依赖关系的目标对象实例注入到应用系统中的
各个关联的组件之中

面向切片编程:AOP

代理技术 通知类型 静态代理 动态代理

简单说就是把那些与业务无关,却为业务模块共同调用的逻辑和责任封装起来,便于减少系统的重复代码,减低模块之间的耦合度,
并有利于未来的可操作性和可维护性。
AOP 把软件系统分为核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的是横切关注点。
横切关注点的特点是,他们经常发生在核心关注点的多处,而各处基本相似,比如权限认证,日志。
AOP的作用是在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。
AOP 应用场景:

  1. Authentication 权限
  2. Caching 缓存
  3. Context passing 内容传递
  4. Error handling 错误处理
  5. Lazy loading 懒加载
  6. Debugging 调试
  7. logging tracing profiling and monitoring 记录跟踪 优化 校准
  8. Performance optimization 性能优化
  9. Persistence 持久化
  10. Resource pooling 资源池
  11. Synchronization 同步
  12. Transactions 事务

常用注解有哪些?

常用注解:
@RestController
@Service
@Repository
@Component
@ReuqestMapping
@AutoWire

spring mvc 改造 成spring boot 注解

spring boot

SpringBoot的一个关键注解是@SpringBootApplication,在这个注解中有三个重要注解:

接收参数注解

@RequestParam (?key=value)和 @PathVariable({value}) 注解是用于从request中接收请求的,两个都可以接收参数,关键点不同的是@RequestParam 是从request里面拿取值,而 @PathVariable 是从一个URI模板里面来填充
@PathVariable URL 后的占位符参数
@PathParam
这个注解是和spring的pathVariable是一样的,也是基于模板的,但是这个是jboss包下面的一个实现,上面的是spring的一个实现,都要导包
@QueryParam
@QueryParam 是 JAX-RS 本来就提供的,和Spring的RequestParam作用一致
@ResponseBody
responseBody表示服务器返回的时候以一种什么样的方式进行返回, 将内容或对象作为 HTTP 响应正文返回,值有很多,一般设定为json
@RequestBody
一般是post请求的时候才会使用这个请求,把参数丢在requestbody里面

重定向和转发

redirect forward

配置文件加载顺序

1、开发者工具 Devtools 全局配置参数;
2、单元测试上的 @TestPropertySource 注解指定的参数;
3、单元测试上的 @SpringBootTest 注解指定的参数;
4、命令行指定的参数,如 java -jar springboot.jar --name="Java技术栈"
5、命令行中的 SPRING_APPLICATION_JSON 指定参数, 如 java -Dspring.application.json='{"name":"Java技术栈"}' -jar springboot.jar
6、ServletConfig 初始化参数;
7、ServletContext 初始化参数;
8、JNDI参数(如 java:comp/env/spring.application.json);
9、Java系统参数(来源:System.getProperties());
10、操作系统环境变量参数;
11、RandomValuePropertySource 随机数,仅匹配:ramdom.*
12、JAR包外面的配置文件参数(application-{profile}.properties(YAML)
13、JAR包里面的配置文件参数(application-{profile}.properties(YAML)
14、JAR包外面的配置文件参数(application.properties(YAML)
15、JAR包里面的配置文件参数(application.properties(YAML)
16、@Configuration配置文件上 @PropertySource 注解加载的参数;
17、默认参数(通过 SpringApplication.setDefaultProperties 指定);

Application属性文件,按优先级排序,位置高的将覆盖位置
  1. 当前项目目录下的一个/config子目录
  2. 当前项目目录
  3. 项目的resources即一个classpath下的/config包
  4. 项目的resources即classpath根路径(root)

Java 面试 - 图2

读取顺序
如果在不同的目录中存在多个配置文件,它的读取顺序是:
1、config/application.properties(项目根目录中config目录下)
2、config/application.yml
3、application.properties(项目根目录下)
4、application.yml
5、resources/config/application.properties(项目resources目录中config目录下)
6、resources/config/application.yml
7、resources/application.properties(项目的resources目录下)
8、resources/application.yml

starter 加载机制

starter 是对依赖的合成 synthesize
redis web jdbc test

传统做法

1)manven引入使用数据库的依赖
2)引入jpa依赖
3)在xml文件中配置属性信息
4)反复调试直到可以正常运行

starter 提升效率

spring boot “约定大于配置”

starter 的实现**:基本都使用两个相同的内容:ConfigurationProperties 和 AutoConfiguration . ConfigurationProperties 用来保存我们的配置,这些配置有一个默认值,不主动覆写情况下,默认值生效、不需要像Spring 一样配置很多xml

整体逻辑
Java 面试 - 图3
ConfigurationProperties还帮助用户减少了无谓的配置操作。并且因为 application.properties 文件的存在,即使需要自定义配置,所有的配置也只需要在一个文件中进行,使用起来非常方便。

服务防止重复提交

  • 客户端防止重复提交

为了防止用户在客户端重复提交表单,要分析从客户端和服务端对重复提交的表单就行处理,首先是客户端处理重复提交表单,使用JavaScript方法,第一种是只允许表单提交一次,后来的不能再提交,第二种是提交一次后按钮变成不可用

  • 服务端防止重复提交


实现原理:
服务器返回表单页面时,会先生成一个subToken保存于session,并把该subToen传给表单页面。当表单提交时会带上subToken,服务器拦截器Interceptor会拦截该请求,拦截器判断session保存的subToken和表单提交subToken是否一致。若不一致或session的subToken为空或表单未携带subToken则不通过。
首次提交表单时session的subToken与表单携带的subToken一致走正常流程,然后拦截器内会删除session保存的subToken。当再次提交表单时由于session的subToken为空则不通过。从而实现了防止表单重复提交。

  1. 自定义防止重复提交标记(@AvoidRepeatableCommit)。
  2. 对需要防止重复提交的Congtroller里的mapping方法加上该注解。
  3. 新增Aspect切入点,为@AvoidRepeatableCommit加入切入点。
  4. 每次提交表单时,Aspect都会保存当前key到reids(须设置过期时间)。
  5. 重复提交时Aspect会判断当前redis是否有该key,若有则拦截。

全局异常处理

@ControllerAdvice
public class GlobalDefultExceptionHandler {

  1. //声明要捕获的异常<br /> @ExceptionHandler(Exception.class)<br /> @ResponseBody<br /> public String defultExcepitonHandler(HttpServletRequest request,Exception e) {<br /> return “error”;<br /> }

过滤器和拦截器

区别

Spring的拦截器与Servlet的Filter有相似之处,比如二者都是AOP编程思想的体现,都能实现权限检查、日志记录等。不同的是:

使用范围不同:Filter是Servlet规范规定的,只能用于Web程序中。而拦截器既可以用于Web程序,也可以用于Application、Swing程序中。

规范不同:Filter是在Servlet规范中定义的,是Servlet容器支持的。而拦截器是在Spring容器内的,是Spring框架支持的。

使用的资源不同:同其他的代码块一样,拦截器也是一个Spring的组件,归Spring管理,配置在Spring文件中,因此能使用Spring里的任何资源、对象,例如Service对象、数据源、事务管理等,通过IoC注入到拦截器即可;而Filter则不能。

深度不同:Filter在只在Servlet前后起作用。而拦截器能够深入到方法前后、异常抛出前后等,因此拦截器的使用具有更大的弹性。所以在Spring构架的程序中,要优先使用拦截器。

实际上Filter和Servlet极其相似,区别只是Filter不能直接对用户生成响应。实际上Filter里doFilter()方法里的代码就是从多个Servlet的service()方法里抽取的通用代码,通过使用Filter可以实现更好的复用。
filter是一个可以复用的代码片段,可以用来转换HTTP请求、响应和头信息。Filter不像Servlet,它不能产生一个请求或者响 应,它只是修改对某一资源的请求,或者修改从某一的响应。
JSR中说明的是,按照多个匹配的Filter,是按照其在web.xml中配置的顺序 来执行的。

过滤器

比较核心的代码是自定义类上面加上@WebFilter,其中@Order注解表示执行过滤顺序,值越小,越先执行spring-boot的入口处加上如下注解@ServletComponentScan:

拦截器

热部署

devtools

Mybatis的工作原理及特点及通配符#和$的区别?

工作原理:

1)读取MyBatis 配置文件
mybatis-config.xml 为MyBatis的全局配置文件,配置了运行环境等信息,例如数据库连接信息
2)加载映射文件
即SQL映射文件,该文件配置了操作数据库的SQL语句,需要在MyBatis配置文件mybatis-config.xml中加载。mybatis-config.xml 文件可以加载多个映射文件,每个文件对应数据库中的一张表
3)构造会话工厂
通过MyBatis的环境等配置信息构建会话工厂SqlSessionFactory
4)创建会话对象
由会话工厂创建SqlSession对象,该对象中包含了执行SQL 语句的所有方法
5)Executor 执行器
6)MappedStatement 对象
7)输入参数映射
8)输出结果映射

特点:

1)sql语句和代码分离,存放在Dao.xml 配置文件
优点:便于维护管理,SQL 语句和java代码分开
缺点:SQL 查询debug 不方便
2) 逻辑标签控制动态SQL 的拼接
优点:标签代理编写逻辑代码
缺点:拼接复杂SQL 语句没有代码灵活
3)提供了对象和数据库字段的映射(查询结果集与Java对象自动映射)
优点:保证名称相同,配置好映射关系即可自动映射 如果不配置映射关系,通过配置列名=字段名 可完成自动映射
缺点:对手写的SQL 依赖性强
4)编写原生SQL
优点:接近JDBC 较灵活
缺点:切换数据源,比如MySQL 迁移到 Oracel ,部分语句要修改

#与$ 的区别

  1. 将传入的数据都当成一个字符串,会对自动传入的数据加一个双引号

  2. $将传入的数值直接显示生成在sql中
  3. 可以很大程度上防止sql注入,$无法防止sql注入

  4. order by 动态参数使用$ 而不能用#
  5. 会导致 MyBatis 创建 PreparedStatement 参数占位符并安全地设置参数 $ 类似于Statement st = conn.createStatement(); ResultSet rs = st.executeQuery(sql);

设计模式

单例模式:全局只有一个对象实例 1)私有化构造器

工厂模式:1)避免通过new 来创建对象 2) 封装对象创建的实现细节

建造模式:复杂对象的构造过程抽象出来,不同实现方法构造出不同属性的对象

适配器

代理模式

linux指令,并说明用途

ls 列出目录下文件
ll 列出文件详细信息
cd 切换目录
cp 复制
mv 重命名或移动
ln 软连接
kill -9 124 杀死进程
nphup & 后台运行
jobs -l 查看后台运行线程
ps -aux
ps -ef|grep java
netstat -anp | grep 8080
tail -f

grep -o ‘KeyWord’ 03.txt | wc -l

redis 底层原理

基本知识

Redis是一个K-V的非关系型数据库(NoSQL),常见的NoSQL数据库有:K-V数据库如Redis、Memcached
优势:
1)读写性能高—100000次/s以上的读速度,80000次/s以上的写速度;
2)K-V value 支持的数据类型很多:字符串String 队列List 哈希Hash 集合Sets 有序集合Sorted Sets 5种不同的数据类型
3) 原子性,Redis的所有操作都是单线程源原子性的
4)支持订阅-发布模式,通知、设置key过期等
5)Redis3.0 版本引入了Redis集群,可用于分布式部署

redis 删除缓存机制

定期删除+惰性删除策略

为什么不是定时删除策略?

定时删除,用一个定时器来负责监控key,当这个key过期就自动删除,虽然内存及时释放,但是十分消耗CPU资源,在大并发请求下CPU要尽可能的把时间都用在处理请求,而不是删除key。因此没有采用这一策略。、、

定期删除+惰性删除如何工作?

定时删除,redis默认每100ms检查是否有过期的key,有过期的key则删除。但是redis不是每个100ms将所有的key检查一次,而是随机抽取进行检查。如果只采用定期策略,会导致很多key到时间没有删除。
使用定时删除会导致删除不完全,于是采用惰性删除。
当在获取key的时候,redis会检查 是否过期,如果这个key设置过期时间并且过期了,此时删除。

采用定期删除和惰性删除是否没有问题了呢?

如果定期删除没有删除key,也没有去及时请求这个key(惰性删除也没有生效),会导致redis的内存越来越高。

内存淘汰策略

maxmemory-policy volatile-lru
1)noeviction 当内存不足以容纳新写入数据时,新写入操作报错
2)allkeys-lru 当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key 项目中在用
3) allkeys-random 当内存不足以容纳新写入的数据,,在键空间中,随机移除某个key
4)volatile-lru 当内存不足以容纳新写入数据时,再设置了过期时间的键空间中,移除最近最少使用的key 该情况一般是把redis既当做缓存 有做持久化存储的时候才用
5)volatile-random 当内存不足以容纳新写入数据时,再设置了过期时间的键空间中,随机移除某个key
6)volatile-ttl 当内存不足以容纳新写入数据时,再设置了过期时间的键空间中,优先删除更早过期时间的key
ps: 如果没有设置expire 的key, volatile 策略行为和 allkeys 基本一致

redis持久化原理和配置详解

为了是redis 在重启后仍能保证数据不丢失,需要将数据从内存中以某种形式持久化到硬盘中。
redis支持两种持久化方式,一种是RDB方式,一种是AOF方式,可以单独使用或者两种结合使用。
快照(RDB文件)和追加式文件(AOF文件)

  • RDN持久化方式会在一个特定的间隔保存那个时间点的数据快照
  • AOF持久化方式会记录每一个服务器收到的写操作。在服务启动时,这些记录的操作会逐条执行从而重建出原来的数据。写操作命令记录的格式跟Redis协议一致,以追加的方式进行保存。
  • Redis持久化可以禁用,让数据的生命周期只存在于服务器的运行时间里
  • 两种持久化可以同时存在,当redis重启时,AOF文件会被优先用于重建数据

RDB概述:
RDB是在某个时间点将数据写入一个临时文件,持久化结束后,用这临时文件替换上次持久化文件,达到数据恢复
优点:使用单线程来进行持久化,主进程不会进行任何IO操作,保证了redis的高性能
缺点:RDB隔一段时间进行持久化,如果持久化之间redis发生故障,会发生数据丢失。这种方式适合数据要求不严谨的情况。 RDB是redis 默认的持久化方式,默认开启。

reids.conf 具体配置参数

#dbfilename:持久化数据存储在本地的文件
dbfilename dump.rdb
`

dir:持久化数据存储在本地的路径,如果是在/redis/redis-3.0.6/src下启动的redis-cli,则数据会存储在当前src目录下<br />

dir ./<br />

snapshot触发的时机,save <br />

如下为900秒后,至少有一个变更操作,才会snapshot<br />

对于此值的设置,需要谨慎,评估系统的变更操作密集程度<br />

可以通过“save “””来关闭snapshot功能<br />

save时间,以下分别表示更改了1个key时间隔900s进行持久化存储;更改了10个key300s进行存储;更改10000个key60s进行存储。<br />

save 900 1<br /> save 300 10<br /> save 60 10000<br />

当snapshot时出现错误无法继续时,是否阻塞客户端“变更操作”,“错误”可能因为磁盘已满/磁盘故障/OS级别异常等<br />

stop-writes-on-bgsave-error yes<br />

是否启用rdb文件压缩,默认为“yes”,压缩往往意味着“额外的cpu消耗”,同时也意味这较小的文件尺寸以及较短的网络传输时间<br />

rdbcompression yes<br />##禁用快照<br />save `""

AOF概述
电脑突然宕机、或者电源断了,或者不小心杀掉进程,那么最新数据就会丢失。
Redis的AOF持久化策略是将发送到Redis服务端的每一条命令都记录下来,并且保存在硬盘的AOF文件中,可以通过参数appendonly来设置是否启用AOF。将“操作+数据”以格式化指令的方式追加到操作日志文件的尾部,在append操作返回后(已经写入到文件或者即将写入),当Server需要数据恢复时,可以直接relay此日志文件,还原所有的操作过程。

##此选项为aof功能的开关,默认为“no”,可以通过“yes”来开启aof功能
##只有在“yes”下,aof重写/文件同步等特性才会生效
appendonly yes
##指定aof文件名称
appendfilename appendonly.aof
##指定aof操作中文件同步策略,有三个合法值:always everysec no,默认为everysec
appendfsync everysec
##在aof-rewrite期间,appendfsync是否暂缓文件同步,"no"表示“不暂缓”,“yes”表示“暂缓”,默认为“no”
no-appendfsync-on-rewrite no
##aof文件rewrite触发的最小文件尺寸(mb,gb),只有大于此aof文件大于此尺寸是才会触发rewrite,默认“64mb”,建议“512mb”
auto-aof-rewrite-min-size 64mb
##相对于“上一次”rewrite,本次rewrite触发时aof文件应该增长的百分比。
##每一次rewrite之后,redis都会记录下此时“新aof”文件的大小(例如A),那么当aof文件增长到A*(1 + p)之后
##触发下一次rewrite,每一次aof记录的添加,都会检测当前aof文件的尺寸。
auto-aof-rewrite-percentage 100

always:每一条aof记录都立即同步到文件,这是最安全的方式,也以为更多的磁盘操作和阻塞延迟,是IO开支较大。
everysec:每秒同步一次,性能和安全都比较中庸的方式,也是redis推荐的方式。如果遇到物理服务器故障,有可能导致最近一秒内aof记录丢失(可能为部分丢失)。
no:redis并不直接调用文件同步,而是交给操作系统来处理,操作系统可以根据buffer填充情况/通道空闲时间等择机触发同步;这是一种普通的文件操作方式。性能较好,在物理服务器故障时,数据丢失量会因OS配置有关。

Redis主从

Java 面试 - 图4

过程:
1:当一个从数据库启动时,会向主数据库发送sync命令,
2:主数据库接收到sync命令后会开始在后台保存快照(执行rdb操作),并将保存期间接收到的命令缓存起来
3:当快照完成后,redis会将快照文件和所有缓存的命令发送给从数据库。
4:从数据库收到后,会载入快照文件并执行收到的缓存的命令。

哨兵

哨兵就是为了监测你的主数据库是否出问题,如果主数据库出问题就会把从数据库升为主数据库,也可以设置多个哨兵来监测主从,然后多个哨兵监测的时候就会运用投票来监测主从,


分布式

分布式 锁
用setnx+expire命令
,Jedis是Redis的Java客户端,除了Jedis之外,Redisson也是Java的客户端,Jedis是阻塞式I/O,而Redisson底层使用Netty可以实现非阻塞I/O,该客户端封装了锁的,继承了J.U.C的Lock接口,所以我们可以像使用ReentrantLock一样使用Redisson,
jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
,通过给value赋值为requestId,我们就知道这把锁是哪个请求加的了,在解锁的时候就可以有依据。requestId可以使用UUID.randomUUID().toString()方法生成。

多线程

实现方式

1)继承Thread 类创建线程、
本质是实现了Runnable接口的一个实例,代表一个线程的实例。启动实例的唯一方法就是Thread 的start 方法。
2)实现Runnable 接口创建线程
如果类已经继承了另一个类,无法直接继承Thread ,可实现Runnable方法
3)实现Callable接口通过FutureTask包装器来创建Thread 线程
Callable<V> oneCallable = ``new`` SomeCallable<V>``();
//``由Callable<Integer>创建一个FutureTask<Integer>对象:
FutureTask<V> oneTask = ``new`` FutureTask<V>``(oneCallable);
//``注释:FutureTask<Integer>是一个包装器,它通过接受Callable<Integer>来创建,它同时实现了Future和Runnable接口。
``//``由FutureTask<Integer>创建一个Thread对象:
Thread oneThread = ``new`` Thread(oneTask);
oneThread.start();
4) 使用ExecutarService Callable Future 实现有返回结果的线程
ExecutorService Callable Future 三个接口属于Exector框架。返回结果的线程在JDK1.5 引入的新特征。
返回值的任务必须实现Callable接口,无返回值的任务必须实现Runnable 接口

多线程如何共享数据

多线程通信主要方式是共享内存,两个关键点:可见性和有序原子性
java 内存模型 (JMM)解决了可见性和有序性的问题,锁解决了原子性的问题。

  1. 将数据抽象成一个类,并将数据的操作作为这个类的方法,在方法加 synchronized 即可 ,然后编写每个方法实现类继承Runnable,
  2. 将Runnable作为一个类的内部类,共享数据作为这个类的成员变量,每个线程对共享数据的操作方法也封装在外部类,然后实现对数据的各个操作的同步和互斥,作为内部类的各个Runnable 对象调用外部类的方法

ThreadLocal

提供线程内的局部变量,在线程的生命周期内起作用,减少同一个线程内多个函数或者组件之间一些公共变量的传递复杂度

线程池

volatile 关键字的作用(变量可见性、禁止重排序)

稍弱的同步机制,用来确保变量的更新操作通知到其他线程。volatile 变量具备两种特性,volatile变量不会被缓存在寄存器或者对其他处理器不可见的地方。在读取时总是返回最新写入的值。

  • 变量可见性

保证该变量对所有线程可见。这里的可见性是指当一个线程修改了变量的值,那么新的值对于其他线程可以立即获取

  • 禁止重排序

禁止了指令重排,使用场景:一个变量被多个线程共享,线程直接给这个变量赋值

非volatile变量读写过程,先copy到cpu缓存。 而volatile 保证了JVM每次从内存中读取变量。
image.png

  • 使用场景

1)对变量的操作不依赖与当前值(i++不行)
2)volatile变量之间没有相互依赖。

synchronized 同步锁

synchronized 可以把任意一个非null的对象当做锁,它属于独占式悲观锁,同时属于可重入锁。

作用范围
  1. 作用于方法时,锁住的是对象的实例(this)
  2. 作用于静态方法时,锁住的是Class实例,又因为Class的相关数据存储在永久代 ,永久代是全局共享的,因此静态方法锁相当于类的一个全局锁,会锁住所有调用该方法的线程
  3. 作用于一个对象实例时,锁住的是所有以该对象为锁的代码块、它有多个队列,当多个线程一起访问某个对象监视器的时候,对象监视器会将这些线程存储在不同的容器中。

    核心组件
  4. Wait Set: 哪些调用wait方法被阻塞的线程被放置在这里

  5. Contention List : 竞争队列,所有请求锁的线程首先被放在这个竞争队列中
  6. Entry List : Contention List 中哪些有资格成为候选资源的线程被移动到Entry List 中
  7. OnDeck: 任意时刻,最多只有一个线程正在竞争锁资源,该线程被称为OnDeck
  8. Owner : 当前已经获取到锁资源的线程
  9. !Owner 当前释放锁的线程

降低锁的粒度

1.减少锁的持有时间(缩小锁的范围)
2.降低锁的请求频率 (锁分解 锁分段)
3.使用带有协调机制的独占锁 (分段锁 读写锁)
独占锁

  • 锁分解
  • 锁分段

并发方式

  • 使用读写锁 ReentrantReadWriteLock 维护一个读锁一个写锁 适用于读比写多的场景


实现

image.png

JVM 原理

线程

内存区域

image.png

内存区域

线程私有区域:程序计数器 、 虚拟机栈 、 本地方法区
虚拟机栈包含了:局部变量表,操作数栈、动态链接、方法出口

线程私有数据区域生命周期和线程生命周期相同
线程共享区域随虚拟机启动 / 关闭而创建/销毁。
直接内存不是JVM运行时数据区的一部分,但也会被频繁使用。

  • 程序计数器

当前线程所执行的字节码的行号指示器,该内存区域是唯一一个在虚拟机中没有规定任何OutOfMemoryError 情况的区域

  • 虚拟机栈

描述java方法执行的内存模型,每个方法在执行的同时,都会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每个方法从调用至执行完成的过程,都对应着一个栈帧在虚拟机栈中入栈和出栈的过程。

  • 本地方法区

作用和虚拟机栈类似,区别在于本地方法栈为Native方法服务。

线程共享区域: 堆 方法区 直接内存
方法区包含了 运行时常量池

  • 堆 (运行时数据区)

线程共享的一块内存区域,创建的对象和数组都保存在java堆内存中,也是垃圾收集器进行垃圾收集的重要内存区域。
从GC角度还可以细分为:新生代(Eden区 From Survivior区和To Survivor区)和老年代。

  • 方法区 1.8 版本 变为元数据空间 metaspace 本地内存

即永久代,用于存储JVM加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
运行时常量池是方法区的一部分,Class 文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池,用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。

image.png

运行时内存

java堆从GC的角度可以细分为 新生代和老年代

垃圾回收与算法

四种引用类型

GC分代收集算法 VS 分区收集算法

  • 标记 - 清除

  • 复制算法 一般内存可能都会浪费

  • 标记-整理算法

  • 分代收集

新生代 采用复制算法
年老代 可能用标记清除 或者其他清除算法
原来是逻辑上区分为老年代和新生代

1.8 G1 不同的内存块,可能分给新生代 也可能分给老年代
image.png

GC垃圾收集器

Java IO/NIO

类加载机制

image.png

  • 加载

找到类的位置,从一个文件或者字节流转为内存中的对象

  • 验证

验证Class 的字节流是否符合虚拟机要求,是否存在危害

  • 准备

类变量 static 修饰的变量 设置初始值

  • 解析

常量值替换为实际值的过程

  • 初始化

执行类中的java代码。和构造方法不同 类中 static 的变量以及 执行静态语句块

  • 使用

  • 卸载

image.png

双亲委派

image.png

Hibernate

Mybatis 缓存

有一级缓存和二级缓存,默认情况下一级缓存开启,而且不能关闭

  • 一级缓存

指SqlSession 级别的缓存,当在同一个SqlSession中进行相同的SQL语句查询时,第二次以后的查询不会从数据库查询,而是直接从缓存中获取,以及缓存最多缓存1024条SQL
原理:第一次发出查询SQL ,sql 查询结果写入sqlsesion 的一级缓存中,缓存使用的数据结构是一个map。同一个SqlSession 再次发出相同的sql ,从缓存中取数据。如果两次中间痴线commit操作(修改、添加、删除)本sqlsession中的以及缓存区域全部清空,下次再去缓存中查询不到,从数据库查询后再写入缓存

key: MapperId+offset+limit+Sql+所有入参
value : 用户信息

  • 二级缓存

指可以跨SqlSession的缓存,是mapper级别(mapper同一个命名空间)的缓存,对于mapper 级别的缓存不同的SqlSession 可以共享。
原理:mapper以命名空间为单位创建缓存数据结构,结构是map。 mybatis的二级缓存通过CacheExecutor实现、所有查询操作,CacheExecutor都会先匹配缓存中是否存在,不存在则查询数据库。
key: MapperId+offest+limit+sql + 所有入参
具体配置:

  1. mybatis全局配置中启用二级缓存配置
  2. 对应mapper.xml中配置cache节点
  3. 对应select 查询节点添加useCache=true

image.png

链表查询

SQL

事务及级别

  • 事务的基本要素 ACID
  1. 原子性: 事务开始后的所有操作,要么昨晚,要么全不做,执行过程中出错回滚到开始状态
  2. 一致性: 事务开始前和结束后,数据库的完整性约束没有被破坏。比如A向B转账,不能A扣钱但是B没收到
  3. 隔离性:同一时间,只允许一个事务请求同一数据,不同事务之间批次之间没有任何干扰。比如A从一张银行卡取钱,在A取钱的过程中,B不能像这种卡转账
  4. 持久性:事务完成后,事务对数据的所有更新将被保存到数据库,不能回滚
  • 事务并发问题
  1. 脏读: 事务A读取了事务B更新的数据,然后B执行回滚操作,A读取到的数据是脏数据
  2. 不可重复读:事务A多次读取同一数据,事务B在事务A的多次读取过程中,对数据做了更新提交,导致事务A多次读取的数据不一致
  3. 幻读:管理员在修改数据库中的学生成绩 ABC,这时候管理员B 插入了一个学生记录D,A修改结束后发现,还有一个记录没有修改,就像发生了幻觉

不可重复读侧重于修改数据,幻读侧重于新增或删除。 解决不可重复读问题需要锁住满足条件的行,解决幻读需要锁表。

  • 事务隔离级别
隔离级别 脏读 不可重复读 幻读
读未提交read-uncommitted
不可重复读read-commited
可重复读 repeatable-read
串行化 serializable

读未提交;
所有事务都能看到其他未提交事务的执行结果。读取未提交数据也被称为脏读

读已提交:
大多数数据库的默认级别,但不是MySQL的默认级别。一个事务只能看见已经提交的事务所做的改变。支持不可重复读,因为同一事务的其他实例在该实例处理期间可能会有新的commit,所以同一select 也可能返回不同的结果。

可重复读:
MySQL默认的隔离级别,它确保同一事务的多个实例在并发读取数据时,会看到同样的数据。 但是理论上会导致幻读。 InnoDB 和 Falcon 存储引擎通过多版本并发控制(《MVCC Multiversion Concurrency Control) 机制解决了问题。

可串行化:
最高级别,通过强制事务排序,使之不可能相互冲突,解决了幻读问题。但是由于在每个读的数据行上加共享锁,可能会导致大量的超时现象和锁竞争。

数据库锁

共享锁 (读锁) 排它锁(写锁)
InnoDB引擎锁机制:支持事务,支持行锁和表锁
5.1 之前版本默认引擎MyISAM,不支持事务,只支持表锁

在oracle数据库中,从下面险种表中数据,按目标结果统计出保单的险种

险种表数据:
险种表 lcpol
contname保单名称 riskname 险种名称
保单1 险种11
保单1 险种12
保单1 险种13
保单2 险种21
保单2 险种22
保单2 险种23
保单3 险种31
保单3 险种32
保单3 险种33

拼接成目标结果:
保单1 险种11,险种12,险种13
保单2 险种21,险种22,险种23
保单3 险种31,险种32,险种33
oracle统计Sql为(任意一种实现方式即可):

SELECT l.contname ,group_contact(l.riskname,‘,’) FROM lcpol l GROUP BY l.contname

select

索引

联合索引又叫复合索引。对于复合索引:Mysql从左到右的使用索引中的字段,一个查询可以只使用索引中的一部份,但只能是最左侧部分。例如索引是key index (a,b,c). 可以支持a | a,b| a,b,c 3种组合进行查找,但不支持 b,c进行查找 .当最左侧字段是常量引用时,索引就十分有效。

两个或更多个列上的索引被称作复合索引。
利用索引中的附加列,您可以缩小搜索的范围,但使用一个具有两列的索引 不同于使用两个单独的索引。复合索引的结构与电话簿类似,人名由姓和名构成,电话簿首先按姓氏对进行排序,然后按名字对有相同姓氏的人进行排序。如果您知 道姓,电话簿将非常有用;如果您知道姓和名,电话簿则更为有用,但如果您只知道名不姓,电话簿将没有用处。
所以说创建复合索引时,应该仔细考虑列的顺序。对索引中的所有列执行搜索或仅对前几列执行搜索时,复合索引非常有用;仅对后面的任意列执行搜索时,复合索引则没有用处。

如果我们创建了(area, age,salary)的复合索引,那么其实相当于创建了(area,age,salary)、(area,age)、(area)三个索引,这被称为最佳左前缀特性。
因此我们在创建复合索引时应该将最常用作限制条件的列放在最左边,依次递减。

NOT IN和操作都不会使用索引将进行全表扫描。NOT IN可以NOT EXISTS代替

如何知道索引生效

explain显示了MySQL如何使用索引来处理select语句以及连接表。可以帮助选择更好的索引和写出更优化的查询语句。
table:显示这一行的数据是关于哪张表的
type:这是重要的列,显示连接使用了何种类型。从最好到最差的连接类型为const、eq_reg、ref、range、indexhe和ALL
possible_keys:显示可能应用在这张表中的索引。如果为空,没有可能的索引。可以为相关的域从WHERE语句中选择一个合适的语句
key: 实际使用的索引。如果为NULL,则没有使用索引。很少的情况下,MYSQL会选择优化不足的索引。这种情况下,可以在SELECT语句中使用USE INDEX(indexname)来强制使用一个索引或者用IGNORE INDEX(indexname)来强制MYSQL忽略索引
key_len:使用的索引的长度。在不损失精确性的情况下,长度越短越好
ref:显示索引的哪一列被使用了,如果可能的话,是一个常数
rows:MYSQL认为必须检查的用来返回请求数据的行数
Extra:关于MYSQL如何解析查询的额外信息。将在表4.3中讨论,但这里可以看到的坏的例子是Using temporary和Using filesort,意思MYSQL根本不能使用索引,结果是检索会很慢
extra列返回的描述的意义
Distinct:一旦MYSQL找到了与行相联合匹配的行,就不再搜索了
Not exists: MYSQL优化了LEFT JOIN,一旦它找到了匹配LEFT JOIN标准的行,就不再搜索了
Range checked for each Record(index map:#):没有找到理想的索引,因此对于从前面表中来的每一个行组合,MYSQL检查使用哪个索引,并用它来从表中返回行。这是使用索引的最慢的连接之一
Using filesort: 看到这个的时候,查询就需要优化了。MYSQL需要进行额外的步骤来发现如何对返回的行排序。它根据连接类型以及存储排序键值和匹配条件的全部行的行指针来排序全部行
Using index: 列数据是从仅仅使用了索引中的信息而没有读取实际的行动的表返回的,这发生在对表的全部的请求列都是同一个索引的部分的时候
Using temporary 看到这个的时候,查询需要优化了。这里,MYSQL需要创建一个临时表来存储结果,这通常发生在对不同的列集进行ORDER BY上,而不是GROUP BY上
Where used 使用了WHERE从句来限制哪些行将与下一张表匹配或者是返回给用户。如果不想返回表中的全部行,并且连接类型ALL或index,这就会发生,或者是查询有问题不同连接类型的解释(按照效率高低的顺序排序)
system 表只有一行:system表。这是const连接类型的特殊情况
const:表中的一个记录的最大值能够匹配这个查询(索引可以是主键或惟一索引)。因为只有一行,这个值实际就是常数,因为MYSQL先读这个值然后把它当做常数来对待
eq_ref:在连接中,MYSQL在查询时,从前面的表中,对每一个记录的联合都从表中读取一个记录,它在查询使用了索引为主键或惟一键的全部时使用
ref:这个连接类型只有在查询使用了不是惟一或主键的键或者是这些类型的部分(比如,利用最左边前缀)时发生。对于之前的表的每一个行联合,全部记录都将从表中读出。这个类型严重依赖于根据索引匹配的记录多少—越少越好
range:这个连接类型使用索引返回一个范围中的行,比如使用>或<查找东西时发生的情况
index: 这个连接类型对前面的表中的每一个记录联合进行完全扫描(比ALL更好,因为索引一般小于表数据)
ALL:这个连接类型对于前面的每一个记录联合进行完全扫描,这一般比较糟糕,应该尽量避免


查询编写和优化

  • 索引

SQL索引有两种,聚集索引和非聚集索引,索引主要目的是提高了SQL Server系统的性能,加快数据的查询速度与减少系统的响应时间

聚集索引和非聚集索引的根本区别是表记录的排列顺序和与索引的排列顺序是否一致

  • 索引为什么能够提高查询速度

创建索引时,你需要确保该索引是应用在 SQL 查询语句的条件(一般作为 WHERE 子句的条件)。
索引也是一张表,该表保存了主键与索引字段,并指向实体表的记录虽然索引大大提高了查询速度,同时却会降低更新表的速度,如对表进行INSERT、UPDATE和DELETE。因为更新表时,MySQL不仅要保存数据,还要保存一下索引文件
索引就是通过事先排好序,从而在查找时可以应用二分查找等高效率的算法

1、无索引,直接去读表数据存放的磁盘块,读到数据缓冲区中再查找需要的数据。
2、有索引,先读入索引表,通过索引表直接找到所需数据的物理地址,并把数据读入数据缓冲区中

索引有什么副作用吗?
(1)索引是有大量数据的时候才建立的,没有大量数据反而会浪费时间,因为索引是使用二叉树建立.
(2)当一个系统查询比较频繁,而新建,修改等操作比较少时,可以创建索引,这样查询的速度会比以前快很多,同时也带来弊端,就是新建或修改等操作时,比没有索引或没有建立覆盖索引时的要慢。
(3)索引并不是越多越好,太多索引会占用很多的索引表空间,甚至比存储一条记录更多。
对于需要频繁新增记录的表,最好不要创建索引,没有索引的表,执行insert、append都很快,有了索引以后,会多一个维护索引的操作,一些大表可能导致insert 速度非常慢。

  • join 联表查询

INNER JOIN:如果表中有至少一个匹配,则返回行
LEFT JOIN:即使右表中没有匹配,也从左表返回所有的行
RIGHT JOIN:即使左表中没有匹配,也从右表返回所有的行
FULL JOIN:只要其中一个表中存在匹配,则返回行

数据库死锁数量

SELECT * FROM information_schema.INNODB_TRX;

Dubbo 分布式

  • 分布式服务框架
  • 高性能和透明化的RPC远程服务调用方案
  • SOA服务治理方案

SPI

消息队列

RPC解决的问题

  • 通讯问题

主要是通过在客户端和服务端之间建立TCP链接,远程过程调用的所有交换的数据都在这个链接里传输。链接可以是按需链接,调用结束后就断掉,也可以是长连接,多个远程过程调用共享同一个链接。

  • 寻址问题

A服务器上的应用怎么告诉底层的RPC框架,如何连接到B服务器以及特定的端口、方法名

  • 序列化和反序列化

A服务器和B服务器之间的相互远程调用时,方法的参数需要通过底层的网络协议如TCP传递到B服务器。由于网络协议是基于二进制的,内存中的参数值要序列化成二进制形式。接收端要进行反序列化。

Dubbo架构

Provider: 暴露服务的服务提供方
Consumer: 调用远程服务的服务消费方
Registry: 服务注册与发现的注册中心
Monitor: 统计服务的调用次数和调用时间的监控中心
Java 面试 - 图14

Dubbo核心服务

  • 远程通讯

提供对多种基于长连接的NIO框架抽象封装,包括多线程模型,序列化,以及“请求-响应”模式的信息交换方式

  • 集群容错

提供基于接口方法的透明远程过程调用,包括多协议支持,以及软负载均衡,失败容错,地址路由,动态配置等集群支持

  • 自动发现

基于注册中心目录服务,使服务消费方能动态的查找服务提供方,使地址透明,使服务提供方可以平滑增加或减少机器。

调用流程
  1. 服务容器负责启动,加载,运行服务提供者
  2. 服务提供者在启动时,向注册中心注册自己提供的服务
  3. 服务消费者在启动时,向注册中心订阅自己所需的服务
  4. 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者
  5. 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用
  6. 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心

注册中心

注册中心几种类型

  • Multicast
  • Zookeeper
  • Redis
  • Simple

了解一个常用RPC框架如Dubbo的实现:服务发现、路由、异步调用、限流降级、失败重试
常见问题

  • Dubbo如何做负载均衡?

内置4中负载均衡策略
1 RandomLoadbalance 随机负载均衡,默认策略(基于权重的负载均衡算法)
随机负载均衡可以通过设置权重,比如让机器性能好的设置更大权重,性能差的设置较小的权重

2 RoundTobinLoadBalance 轮询负载均衡
依次调用所有的provider. 虽然也有权重的概念,可以让RPC调用严格按照我们设置的比例来分配,但是会存在慢的Provider 累积请求的问题。

3 LeastActiveLoadBalance 最少活跃调用数,相同活跃数的随机。 活跃数指调用前后计数差,使慢的Provider收到更少请求
每个服务维护一个活跃计数器,当开始请求时加1 请求结束减1 把请求分配给活跃计数器少的Provider

4 ConsistentHashLoadBalance 一致性哈希负载均衡。相同参数请求总是落在同一台机器
使用一致性Hash算法,让相同参数的请求总是发到同一Provider。一致Hash可以和缓存机制配合使用,比如获取用户信息服务,可以把用户信息缓存起来,减少数据库查询。

5.自定实现:
实现LoadBalance接口, 以下是Dubbo的LoadBalance接口:

相关配置优先级:
1 方法优先,接口次之,全局配置再次之
2 如果级别一样,则消费方优先,提供方次之
四种配置的优先级:

  1. 客户端方法级别
  2. 客户端接口级别
  3. 服务端方法级别
  4. 客户端接口级别
  • Dubbo如何做限流降级?

通过Sentinel 来监控流量,来进行流量控制、熔断降级
Sentinel 是阿里中间件团队开源的,面向分布式服务架构的轻量级流量控制产品,主要以流量为切入点,从流量控制、熔断降级、系统负载保护等维度保护服务的稳定性。

引入此依赖后,Dubbo 的服务接口和方法(包括调用端和服务端)就会成为 Sentinel 中的资源,在配置了规则后就可以自动享受到 Sentinel 的防护能力。同时提供了灵活的配置选项,

最佳实践:
1)基于Provider 配饰QPS模式限流 每秒请求数
限流粒度可以是 服务接口服务方法 两种粒度

2)基于Service Consumer
并发线程数限流 配置线程数模式 保证自身不被不稳定的服务影响
服务降级 当服务依赖多个下游服务,而某个下游服务会严重影响当前服务的调用,可以利用熔断降级功能,为调用配置基于平均RT的降级规则

  • Dubbo如何优雅的下线服务?

目前Tomcat / Undertoe / Dubbo 等容器/框架都有提供相关实现。
优雅停机的定义:指在停止应用时,执行的一系列保证应用正常关闭的操作。这些操作往往包括等待已有请求执行完成、关闭线程、关闭连接和释放资源等。优雅停机可以避免非正常关闭程序可能造成的数据异常或丢失,应用异常等问题。本质是JVM即将关闭前执行的一些额外的处理代码

使用场景:

  • JVM主动关闭(System.exit(int))
  • JVM由于资源问题退出(OOM)
  • 应用程序接收到SIGTERM或SIGINT信号

配置方式
1) 优雅停机默认开启
dubbo.service.shutdown.wait=20000 默认时间是10000毫秒
2)容器的优雅停机
当使用org.apache.dubbo.container.Main这种容器方式来使用 Dubbo 时,也可以通过配置dubbo.shutdown.hooktrue来开启优雅停机。
3)通过QOS优雅上下线
通过shutdownhook 方式无法确保所有关闭流才能一定执行完毕,所以推出了多段关闭的方式保证服务完全无损。

多段关闭流程:
Provider 接收到停机命令后:

  • 从注册中心上注销所有的服务
  • 从配置中心取消监听动态配置
  • 向所有链接的客户端发送只读事件,停止接收新请求
  • 等待一段时间以处理已到达的请求,然后关闭请求处理线程池
  • 断开所哟客户端连接

Consumer 接收到停机指令后

  • 拒绝新到请求,直接返回调用异常
  • 等待当前已发送请求执行完毕,如果响应超时则强制关闭连接

当使用容器方式运行 Dubbo 时,在容器准备退出前,可进行一系列的资源释放和清理工。
例如使用 SpringContainer时,Dubbo 的ShutdownHook线程会执行ApplicationContextstopclose方法,保证 Bean的生命周期完整。

  • Dubbo如何实现异步调用的?

    2.6 版本之前使用Future实现
    2.7 版本和 CompletableFuture 类型接口
    重载同步接口
    使用AsyncContext

从3.0.0版本开始,Dubbo框架提供了对Reactive编程范式的支持,除了编程接口之外,在跨进程的RPC通信中引入了Reactive的语义。如果你所在的环境需要使用Reactive编程范式,或者你的RPC调用需要支持流式传输,Reactive应该会给你带来帮助,

注意事项

  • 使用SIGKILL关闭应用不会执行优雅停机;
  • 优雅停机不保证会等待所有已发送/到达请求结束;
  • 配置的优雅停机等待时间timeout不是所有步骤等待时间的总和,而是每一个destroy执行的最大时间。例如配置等待时间为5秒,则关闭Server、关闭Client等步骤会分别等待5秒。


Zookeeper

业务贷款