自己整理了 java相关的面试题
这里面大部分都是摘自其他文章,在此感谢各位面试题的提供者
目前全部存放在了一片文章当中,以后会进行分类
JVM
java虚拟机
其主要是用来执行java字节码(二进制的形式)的虚拟计算机。运行在操作系统之上的,与硬件没有任何关系。
JDK
Java开发工具包(Java Development Kit),其包含包括 Java 运行环境(Java Runtime Envirnment,简称 JRE),Java 工具(比如 javac、java、javap 等等),以及 Java 基础类库(比如 rt.jar)
JRE
他包含运行JAVA程序所必须的环境的集合,包含JVM标准实现及Java核心类库。
JMM
Java内存模型,一个抽象的概念,主要在并发编程时用到,具有原子性,有序性,一致性。
java代码需要编译成字节码文件才能被计算机执行,字节码的开头是CAFFBABE,正好对应Java的图标。
当然是可以的,char类型中存储的是Unicode编码,Unicode编码中是存在中文的,所以char自然可以存储汉字,但是!仅限于Unicode中存在的汉字。
一个汉字的占两个字节,一个Unicode也是占两个字节 ,char存储汉字完全没有问题。
简单来说,所有的非基本数据类型都是引用数据类型,除了基本数据类型对应的引用类型外,类、 接口类型、 数组类型、 枚举类型、 注解类型、 字符串型都属于引用类型。
主要有以下区别:
存储位置
传递方式
局部变量 基本类型存放在栈中 引用类型存放在堆中
之所以说 值类型的实例一般分配在 栈上而非 全部分配在栈上的原因是由一下几种情况, 值类型也有可能分配在 堆上。
这些特殊的情况包括数数组的元素、引用类型中的值类型字段、迭代器块中的局部变量、闭包情况下匿名函数( Lamda )中的局部变量。这是由于在这几种情况下的值类型实例如果分配在 线程栈上,有可能会出现线程栈中的方法己经调用结束,但是还会访问这些值的情况。也就是说如果分配在 线程栈上,有可能会随着被调用方法的返回而被清除掉。因此它们也被分配在了托管堆上,以满足在方法返回之后还能够被访问的要求。所以单纯地说“引用类型保存在托管堆上,值类型保存在线程栈上”是不准确的。将这句话一分为二看待,引用类型的确总是分配在托管堆上, 但是值类型并非总是分配在线程栈上有可能分配在堆上。
类变量(静态变量)存在于方法区!!引用类型的局部变量声明在栈,存储在堆
总的来说:我们先来记住两条黄金法则:
1.引用类型总是被分配到“堆”上。不论是成员变量还是局部
2.基础类型总是分配到它声明的地方:成员变量在堆内存里,局部变量在栈内存里。
我们都知道==操作符用来两个对象的地址是否相同,即是否是指相同一个对象。
equals()比较的两个对象的值是否相同,不管是不是一个对象。
但其实object类下的equals()和==是一样的,我们用的都是被重写之后的。
三者共同之处:
都是final类,不允许被继承,所以string每次改变值都会新建一个对象。
StringBuffer是线程安全,可以不需要额外的同步用于多线程中;
StringBuilder是非同步,运行于多线程中就需要使用着单独同步处理,但是速度就比StringBuffer快多了;
StringBuffer与StringBuilder两者共同之处:可以通过append、indert进行字符串的操作。
抽象类:
抽象类是特殊的类,只是不能被实例化;除此以外,具有类的其他特性;重要的是抽象类可以包括抽象方法,这是普通类所不能的。抽象方法只能声明于抽象类中,且不包含任何实现,派生类必须覆盖它们。另外,抽象类可以派生自一个抽象类,可以覆盖基类的抽象方法也可以不覆盖,如果不覆盖,则其派生类必须覆盖它们。
接口:
接口是引用类型的,类似于类,和抽象类的相似之处有三点:
1. 不能实例化;
2. 包含未实现的方法声明;
3. 派生类必须实现未实现的方法,抽象类是抽象方法,接口则是所有成员(不仅是方法包括其他成员);
另外,接口有如下特性:
接口除了可以包含方法之外,还可以包含属性、索引器、事件,而且这些成员都被定义为公有的。除此之外,不能包含任何其他的成员,例如:常量、域、构造函数、析构函数、静态成员。一个类可以直接继承多个接口,但只能直接继承一个类(包括抽象类)。
抽象类和接口的区别:
抽象类和接口的使用:
大白话:
接口是抽象行为 跨类调用
抽象类 抽象行为和知识
概念不一样,接口是对动作的抽象,抽象类是对根源的抽象。你关注一个事物本质的时候,用抽象类,关注一个操作的时候,用接口。
默认 : public+static+final
final翻译成中文是“不可更改的,最终的”,顾名思义,他的功能就是不能再修改,不能再继承。我们常见的String类就是被final修饰的。
将类、方法、变量声明为final能够提高性能,这样JVM就有机会进行估计,然后优化。
按照Java代码惯例,final变量就是常量,而且通常常量名要大写:
方法的重载和重写都是实现多态的方式,区别在于前者实现的是编译时的多态性,而后者实现的是运行时的多态性。
重载:发生在同一个类中,方法名相同参数列表不同(参数类型不同、个数不同、顺序不同),与方法返回值和访问修饰符无关,即重载的方法不能根据返回类型进行区分
重写:发生在父子类中,方法名、参数列表必须相同,返回值小于等于父类,抛出的异常小于等于父类,访问修饰符大于等于父类(里氏代换原则);如果父类方法访问修饰符为private则子类中就不是重写。
构造器不能被继承,因此不能被重写,但可以被重载。
泛”就是宽泛,泛指的意思,所谓泛型就是不指定具体类型,而是作为参数传递。
泛型的优点:
使用泛型类时指明了数据类型,赋给其他类型的值会抛出异常,既不需要向下转型,也没有潜在的风险。
除了定义泛型类,还可以定义泛型接口和泛型方法,使用泛型方法时不必指明参数类型,编译器会根据传递的参数自动查找出具体的类型。
限制泛型的可用类型:
通过 extends 关键字可以限制泛型的类型
泛型代码与JVM:
虚拟机中没有泛型,只有普通类和方法。
在编译阶段,所有泛型类的类型参数都会被Object或者它们的限定边界来替换。(类型擦除)
在继承泛型类型的时候,桥方法的合成是为了避免类型变量擦除所带来的多态灾难。
对象(new)是对概念(class)的具象产物
封装 方法和方法需要的知识(成员变量)放到一起
对象需要有 行为(方法)和知识(成员变量)
继承
继承的主要思想就是将子类的对象作为父类的对象来使用。比如王者荣耀的英雄作为父类,后裔作为子类。后裔有所有英雄共有的属性,同时也有自己独特的技能。
多态
多态的定义:
指允许不同类的对象对同一消息做出响应。即同一消息可以根据发送对象的不同而采用多种不同的行为方式。(发送消息就是函数调用)
简单来说,同样调用攻击这个方法,后裔的普攻和亚瑟的普攻是不一样的。
多态的条件:
要有继承
要有重写
父类引用指向子类对象
多态的好处:
多态对已存在代码具有可替换性。
多态对代码具有可扩充性。
它在应用中体现了灵活多样的操作,提高了使用效率。
多态简化对应用软件的代码编写和修改过程,尤其在处理大量对象的运算和操作时,这个特点尤为突出和重要。
public interface Animal {
default void fly() {
System.out.println("birds can fly...");
}
default void swim() {
System.out.println("fishes can swim......");
}
}
public class Bird implements Animal {
}
public class TestMain {
public static void main(String[] args) {
Bird bird = new Bird();
bird.fly();
Fish fish = new Fishe();
fish.swim();
}
}
public interface AnimalFactory {
static Animal create(Supplier<Animal> supplier) {
return supplier.get();
}
}
public class TestAnimalFactory {
public static void main(String[] args) {
// 生产一只鸟
Animal bird = AnimalFactory.create(Bird::new);
bird.fly();
// 生产一条鱼
Animal fish = AnimalFactory.create(Fishe::new);
fish.swim();
}
}
@FunctionalInterface
interface GreetingService
{
void sayMessage(String message);
}
// 1. 不需要参数,返回值为 5
() -> 5
// 2. 接收一个参数(数字类型),返回其2倍的值
x -> 2 * x
// 3. 接受2个参数(数字),并返回他们的差值
(x, y) -> x – y
// 4. 接收2个int型整数,返回他们的和
(int x, int y) -> x + y
// 5. 接受一个 string 对象,并在控制台打印,不返回任何值(看起来像是返回void)
(String s) -> System.out.print(s)
public class Java8Tester {
public static void main(String args[]){
Java8Tester tester = new Java8Tester();
// 类型声明
MathOperation addition = (int a, int b) -> a + b;
// 不用类型声明
MathOperation subtraction = (a, b) -> a - b;
// 大括号中的返回语句
MathOperation multiplication = (int a, int b) -> { return a * b; };
// 没有大括号及返回语句
MathOperation division = (int a, int b) -> a / b;
System.out.println("10 + 5 = " + tester.operate(10, 5, addition));
System.out.println("10 - 5 = " + tester.operate(10, 5, subtraction));
System.out.println("10 x 5 = " + tester.operate(10, 5, multiplication));
System.out.println("10 / 5 = " + tester.operate(10, 5, division));
// 不用括号
GreetingService greetService1 = message ->
System.out.println("Hello " + message);
// 用括号
GreetingService greetService2 = (message) ->
System.out.println("Hello " + message);
greetService1.sayMessage("Runoob");
greetService2.sayMessage("Google");
}
interface MathOperation {
int operation(int a, int b);
}
interface GreetingService {
void sayMessage(String message);
}
private int operate(int a, int b, MathOperation mathOperation){
return mathOperation.operation(a, b);
}
}
执行结果:
$ javac Java8Tester.java
$ java Java8Tester
10 + 5 = 15
10 - 5 = 5
10 x 5 = 50
10 / 5 = 2
Hello Runoob
Hello Google
lambda 表达式只能引用标记了 final 的外层局部变量,这就是说不能在 lambda 内部修改定义在域外的局部变量,否则会编译错误。
扩容机制
- 初始化 new ArrayList(),在jdk1.6之前默认容量是10.在容量超过当前容量时进行扩容,扩容大小为当前的1.5倍+1
- jdk1.7之后默认容量是0,在第一次add时扩容为10,add超过当前容量时再次扩容,扩容大小为当前容量的1.5倍
- 当初始化时指定容量时new ArrayList(20),在初始化就生成指定容量的list,所以并未扩容
- ArrayList底层是数组,每次扩容是生成新的数组并将之前list中的数组赋值到当前数组中
常用List集合
- ArrayList
- 基于数组实现的非线程安全的集合。查询任意位置元素都很快,插入或删除头部元素比linkedList慢的多。
- Jdk1.7之前ArrayList默认大小是10 扩容大小为当前的1.5倍+1,JDK1.7之后是0,第一次add时扩容为10,每次约按1.5倍扩容。
- 当初始化时指定容量时new ArrayList(20),在初始化就生成指定容量的list,所以并未扩容
- ArrayList底层是数组,每次扩容是生成新的数组并将之前list中的数组赋值到当前数组中
- LinkedList
- 基于链表实现的非线程安全的集合。查询中间元素比ArrayList慢的多,其他差距不大。插入或删除中间元素比ArrayList慢的多。
- Vector
- Vector是线程安全的
- Vector 每次扩容都以当前数组大小的 2 倍去扩容
int i = Integer.MAX_VALUE;
System.out.println(i+1);//结果-2147483648
因为在java中整型值是有范围的,它的最大值为2^31
-1,也就是2147483647,最小值是-2^31-1,也就是-2147483648。
当对最大值进行+1时,就变成2147483648(越界了),就溢出了,那么此值为多少呢?结果是-2147483648,即为Integer.MIN_VALUE,所以就有了Integer.MAX_VALUE + 1=Integer.MIN_VALUE
初始(NEW):新创建了一个线程对象,但还没有调用start()方法。
运行(RUNNABLE):Java线程中将就绪(ready)和运行中(running)两种状态笼统的称为“运行”。
线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取CPU的使用权,此时处于就绪状态(ready)。就绪状态的线程在获得CPU时间片后变为运行中状态(running)。
阻塞(BLOCKED):表示线程阻塞于锁。
等待(WAITING):进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)。
超时等待(TIMED_WAITING):该状态不同于WAITING,它可以在指定的时间后自行返回。
终止(TERMINATED):表示该线程已经执行完毕。
IOC 被称为控制反转 或 依赖注入
白话: spring将配置为bean的对象放入ioc容器中,并且需要的地方将对象进行注入
select p.*,b.supplier,t.type,c.org from
(select po.id from porder po where po.mark = 0 order by po.id desc limit 800000,500) a
inner join porder p on a.id = p.id and p.mark = 0
left JOIN brand b on p.supplier = b.supplier_id and b.mark = 0
left JOIN purchase c on p.org = c.id and c.mark = 0
left JOIN type t on c.category = t.type_id and t.mark = 0;
select id from t where num=10 or Name = 'admin'
#可以这样查询:
select id from t where num = 10
union all
select id from t where Name = 'admin'
根本的原因是程序逻辑的顺序,最常见的是交差更新
Transaction 1: 更新表A -> 更新表B
Transaction 2: 更新表B -> 更新表A
这类问题要从程序上避免,所有的更新需要按照一定的顺序
正常情况:
SELECT account, nick_name FROM account
执行结果:
account nick_name sysAdmin 管理员 chichi1 痴痴 行转列:
SELECT GROUP_CONCAT( account ) account, GROUP_CONCAT( nick_name ) nick_name FROM account
执行结果:
account nick_name sysAdmin,chichi1 管理员,痴痴
- LEFT JOIN 或 RIGHT JOIN 或 FULL JOIN 会报语法错误
- INNER JOIN 或 CROSS JOIN 返回被连接的两个表的笛卡尔积,返回结果的行数等于两个表行数的乘积
eg:拿转账来说,假设用户A和用户B两者的钱加起来一共是20000,那么不管A和B之间如何转账,转几次账,事务结束后两个用户的钱相加起来应该还得是20000,这就是事务的一致性。
一致性(Consistency):事务的执行使数据从一个状态转换为另一个状态,但是对于整个数据的完整性保持稳定。
隔离性(Isolation):隔离性是当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。 即要达到这么一种效果:对于任意两个并发的事务T1和T2,在事务T1看来,T2要么在T1开始之前就已经结束,要么在T1结束之后才开始,这样每个事务都感觉不到有其他事务在并发地执行。
持久性(Durability):当事务正确完成后,它对于数据的改变是永久性的。
eg: 例如我们在使用JDBC操作数据库时,在提交事务方法后,提示用户事务操作完成,当我们程序执行完成直到看到提示后,就可以认定事务以及正确提交,即使这时候数据库出现了问题,也必须要将我们的事务完全执行完成,否则就会造成我们看到提示事务处理完毕,但是数据库因为故障而没有执行事务的重大错误。
同数据库实例可以直接使用库名调用
非同数据库可以使用FEDERATED引擎进行映射表
InnoDB支持事务,MyISAM不支持,对于InnoDB每一条SQL语言都默认封装成事务,自动提交,这样会影响速度,所以最好把多条SQL语言放在begin和commit之间,组成一个事务;
InnoDB支持外键,而MyISAM不支持。对一个包含外键的InnoDB表转为MYISAM会失败;
InnoDB是聚集索引,使用B+Tree作为索引结构,数据文件是和(主键)索引绑在一起的(表数据文件本身就是按B+Tree组织的一个索引结构),必须要有主键,通过主键索引效率很高。但是辅助索引需要两次查询,先查询到主键,然后再通过主键查询到数据。因此,主键不应该过大,因为主键太大,其他索引也都会很大
InnoDB不保存表的具体行数,执行select count(*) from table时需要全表扫描。而MyISAM用一个变量保存了整个表的行数,执行上述语句时只需要读出该变量即可,速度很快(注意不能加有任何WHERE条件);
Innodb不支持全文索引,而MyISAM支持全文索引,在涉及全文索引领域的查询效率上MyISAM速度更快高;PS:5.7以后的InnoDB支持全文索引了
MyISAM表格可以被压缩后进行查询操作
InnoDB支持表、行(默认)级锁,而MyISAM支持表级锁
InnoDB表必须有唯一索引(如主键)(用户没有指定的话会自己找/生产一个隐藏列Row_id来充当默认主键),而Myisam可以没有
Innodb存储文件有frm、ibd,而Myisam是frm、MYD、MYI
innodb:frm是表定义文件,ibd是数据文件
Myisam:frm是表定义文件,myd是数据文件,myi是索引文件
是否要支持事务,如果要请选择innodb,如果不需要可以考虑MyISAM;
如果表中绝大多数都只是读查询,可以考虑MyISAM,如果既有读也有写,请使用InnoDB。
系统奔溃后,MyISAM恢复起来更困难,能否接受;
MySQL5.5版本开始Innodb已经成为Mysql的默认引擎(之前是MyISAM),说明其优势是有目共睹的,如果你不知道用什么,那就用InnoDB,至少不会差。
netstat命令各个参数说明如下:
-t : 指明显示TCP端口
-a : 显示所有的套接字
-u : 指明显示UDP端口
-l : 仅显示监听套接字(所谓套接字就是使应用程序能够读写与收发通讯协议(protocol)与资料的程序)
-p : 显示进程标识符和程序名称,每一个套接字/端口都属于一个程序。
-n : 不进行DNS轮询,显示IP(可以加速操作)
从Producer分析:如何确保消息正确的发送到了Broker?
从Broker分析:如果确保接收到的消息不会丢失?
从Cunmser分析:如何确保拉取到的消息被成功消费?
影响消息正常发送和消费的重要原因是网络的不确定性。
引起重复消费的原因
ACK
正常情况下在consumer真正消费完消息后应该发送ack,通知broker该消息已正常消费,从queue中剔除
当ack因为网络原因无法发送到broker,broker会认为词条消息没有被消费,此后会开启消息重投机制把消息再次投递到consumer
消费模式
在CLUSTERING模式下,消息在broker中会保证相同group的consumer消费一次,但是针对不同group的consumer会推送多次
解决方案
数据库表
保证每条消息都有唯一编号且保证消息处理成功与去重表的日志同时出现
利用一张日志表来记录已经处理成功的消息的ID,如果新到的消息ID已经在日志表中,那么就不再处理这条消息。
Map
单机时可以使用map ConcurrentHashMap -> putIfAbsent guava cache
Redis
分布式锁搞起来。
MQ | 描述 |
---|---|
RabbitMQ | erlang开发,对消息堆积的支持并不好,当大量消息积压的时候,会导致 RabbitMQ 的性能急剧下降。每秒钟可以处理几万到十几万条消息。 |
RocketMQ | java开发,面向互联网集群化功能丰富,对在线业务的响应时延做了很多的优化,大多数情况下可以做到毫秒级的响应,每秒钟大概能处理几十万条消息。 |
Kafka | Scala开发,面向日志功能丰富,性能最高。当你的业务场景中,每秒钟消息数量没有那么多的时候,Kafka 的时延反而会比较高。所以,Kafka 不太适合在线业务场景。 |
ActiveMQ | java开发,简单,稳定,性能不如前面三个。小型系统用也ok,但是不推荐。推荐用互联网主流的 |
因为项目比较大,做了分布式系统,所有远程服务调用请求都是同步执行经常出问题,所以引入了mq
作用 | 描述 |
---|---|
解耦 | 系统耦合度降低,没有强依赖关系 |
异步 | 不需要同步执行的远程调用可以有效提高响应时间 |
削峰 | 请求达到峰值后,后端service还可以保持固定消费速率消费,不会被压垮 |
角色 | 作用 |
---|---|
Nameserver | 注册中心,Broker会向所有的NameServer上注册自己的信息无状态,动态列表;这也是和zookeeper的重要区别之一。zookeeper是有状态的。 |
Producer | 消息生产者,负责发消息到Broker。 |
Broker | 就是MQ本身,负责收发消息、持久化消息等。 |
Consumer | 消息消费者,负责从Broker上拉取消息进行消费,消费完进行ack。 |
不会,每条消息都会持久化到CommitLog中,每个Consumer连接到Broker后会维持消费进度信息,当有消息消费后只是当前Consumer的消费进度(CommitLog的offset)更新了。
追问:那么消息会堆积吗?什么时候清理过期消息?
4.6版本默认48小时后会删除不再使用的CommitLog文件
检查这个文件最后访问时间
判断是否大于过期时间
指定时间删除,默认凌晨4点
消费模型由Consumer决定,消费维度为Topic。
集群消费
1.一条消息只会被同Group中的一个Consumer消费
2.多个Group同时消费一个Topic时,每个Group都会有一个Consumer消费到数据
广播消费
消息将对一 个Consumer Group 下的各个 Consumer 实例都消费一遍。即即使这些 Consumer 属于同一个Consumer Group ,消息也会被 Consumer Group 中的每个 Consumer 都消费一次。
RocketMQ没有真正意义的push,都是pull,虽然有push类,但实际底层实现采用的是长轮询机制,即拉取方式
broker端属性 longPollingEnable 标记是否开启长轮询。默认开启
追问:为什么要主动拉取消息而不使用事件监听方式?
事件驱动方式是建立好长连接,由事件(发送数据)的方式来实时推送。
如果broker主动推送消息的话有可能push速度快,消费速度慢的情况,那么就会造成消息在consumer端堆积过多,同时又不能被其他consumer消费的情况。而pull的方式可以根据当前自身情况来pull,不会造成过多的压力而造成瓶颈。所以采取了pull的方式。
一个index是由若干个segment组成,随着每个segment的不断增长,我们索引一条数据后可能要经过分钟级别的延迟才能被搜索,为什么有种这么大的延迟,这里面的瓶颈点主要在磁盘。
持久化一个segment需要fsync操作用来确保segment能够物理的被写入磁盘以真正的避免数据丢失,但是fsync操作比较耗时,所以它不能在每索引一条数据后就执行一次,如果那样索引和搜索的延迟都会非常之大。
所以这里需要一个更轻量级的处理方式,从而保证搜索的延迟更小。这就需要用到上面提到的FileSystem Cache,所以在es中新增的document会被收集到indexing buffer区后被重写成一个segment然后直接写入filesystem cache中,这个操作是非常轻量级的,相对耗时较少,之后经过一定的间隔或外部触发后才会被flush到磁盘上,这个操作非常耗时。但只要sengment文件被写入cache后,这个sengment就可以打开和查询,从而确保在短时间内就可以搜到,而不用执行一个full commit也就是fsync操作,这是一个非常轻量级的处理方式而且是可以高频次的被执行,而不会破坏es的性能。
在elasticsearch里面,这个轻量级的写入和打开一个cache中的segment的操作叫做refresh,默认情况下,es集群中的每个shard会每隔1秒自动refresh一次,这就是我们为什么说es是近实时的搜索引擎而不是实时的,也就是说给索引插入一条数据后,我们需要等待1秒才能被搜到这条数据,这是es对写入和查询一个平衡的设置方式,这样设置既提升了es的索引写入效率同时也使得es能够近实时检索数据。
refresh操作相比commit操作是非常轻量级的但是它仍然会耗费一定的性能,所以不建议在每插入一条数据后就执行一次refresh命令,es默认的1秒的延迟对于大多数场景基本都可以接受。
简洁回答:当我们把一条数据写入到Elasticsearch中后,它并不能马上被用于搜索。新增的索引必须写入到Segment后才能被搜索到,因此我们把数据写入到内存缓冲区之后并不能被搜索到。新增了一条记录时,Elasticsearch会把数据写到translog和in-memory buffer(内存缓存区)中,如果希望该文档能立刻被搜索,需要手动调用refresh操作。在Elasticsearch中,默认情况下_refresh操作设置为每秒执行一次。 在此操作期间,内存中缓冲区的内容将复制到内存中新创建的Segment中,操作如下
#手动刷新my_logs索引。
POST /my_logs/_refresh
#每个节点都要进行设置
#当前节点名称
node.name: es-node1
#是否有资格成为主节点
node.master: true
#是否存储数据
node.data: true
#写入候选节点 包括当前节点,在开启服务后可以被选为主节点
discovery.seed_hosts: ["10.100.0.7", "10.100.0.8"]
#通过此配置选举主节点
cluster.initial_master_nodes: ["es-node1","es-node2"]
删除和更新也都是写操作,但是 Elasticsearch 中的文档是不可变的,因此不能被删除或者改动以展示其变更。
磁盘上的每个段都有一个相应的 .del 文件。当删除请求发送后,文档并没有真的被删除,而是在 .del 文件中被标记为删除。该文档依然能匹配查询,但是会在结果中被过滤掉。当段合并时,在 .del 文件中被标记为删除的文档将不会被写入新段。
在新的文档被创建时,Elasticsearch 会为该文档指定一个版本号,当执行更新时,旧版本的文档在 .del 文件中被标记为删除,新版本的文档被索引到一个新段。旧版本的文档依然能匹配查询,但是会在结果中被过滤掉。
CAP定理: 指的是在一个分布式系统中,Consistency(一致性)、 Availability(可用性)、Partition tolerance(分区容错性),三者不可同时获得
CAP理论就是说在分布式存储系统中,最多只能实现上面的两点。而由于当前的网络硬件肯定会出现延迟丢包等问题,所以分区容忍性是我们必须需要实现的。所以我们只能在一致性和可用性之间进行权衡
nacos :
eureka:
支持
@RefreshScope 添加到类上,就是说明在项目运行过程中,如果远程配置文件有做修改,修改的信息在@RefreshScope添加的类中有使用,就会将原有的实例注销,在重新创建一个新的实例运行。这样就能保证在项目不重启的情况下读取到最新的修改信息。如果修改的配置信息在没有添加@RefreshScope的类中使用,这个类的实例就不会注销,所以不管怎么修改,没有加@RefreshScope的类请求结果一直都会是项目运行时加载的旧数据。
需要将每个nacos server 实例配置mysql(conf目录下的nacos-mysql.sql文件)
需要将每个nacos server 配置一份集群节点信息,配置文件在conf目录下的cluster.conf.example文件,我们进行重命名成cluster.conf。 然后编辑cluster.conf文件,增加3个节点的信息,格式为IP:PORT,三个目录都一致即可。
启动的话直接到bin目录下,执行./startup.sh就可以了,默认就是集群模式,不需要加任何参数
Nacos Server 的数据源是用 Derby 或 MySQL 完全是由其运行模式决定的:
standalone 的话仅会使用 Derby,即使在 application.properties 里边配置 MySQL 也照样无视;
cluster 模式会自动使用 MySQL,这时候如果没有 MySQL 的配置,是会报错的。
Nacos注册中心分为server与client,server采用Java编写,为client提供注册发现服务与配置服务。而client可以用多语言实现,client与微服务嵌套在一起,nacos提供sdk和openApi,如果没有sdk也可以根据openApi手动写服务注册与发现和配置拉取的逻辑
Nacos服务领域模型主要分为命名空间、集群、服务。在下图的分级存储模型可以看到,在服务级别,保存了健康检查开关、元数据、路由机制、保护阈值等设置,而集群保存了健康检查模式、元数据、同步机制等数据,实例保存了该实例的ip、端口、权重、健康检查状态、下线状态、元数据、响应时间
服务注册方法:以Java nacos client v1.0.1 为例子,服务注册的策略的是每5秒向nacos server发送一次心跳,心跳带上了服务名,服务ip,服务端口等信息。同时 nacos server也会向client 主动发起健康检查,支持tcp/http检查。如果15秒内无心跳且健康检查失败则认为实例不健康,如果30秒内健康检查失败则剔除实例。
Nacos主要提供以下四大功能:
服务发现与服务健康检查
Nacos使服务更容易注册,并通过DNS或HTTP接口发现其他服务,Nacos还提供服务的实时健康检查,以防止向不健康的主机或服务实例发送请求。
动态配置管理
动态配置服务允许您在所有环境中以集中和动态的方式管理所有服务的配置。Nacos消除了在更新配置时重新部署应用程序,这使配置的更改更加高效和灵活。
动态DNS服务
Nacos提供基于DNS协议的服务发现能力,旨在支持异构语言的服务发现,支持将注册在Nacos上的服务以域名的方式暴露端点,让三方应用方便地查阅及发现。
服务和元数据管理
Nacos能让您从微服务平台建设的视角管理数据中心的所有服务及元数据,包括管理服务的描述、生命周期、服务的静态依赖分析、服务的健康状态、服务的流量管理、路由及安全策略。
Feign是声明性Web服务客户端。它使编写Web服务客户端更加容易。要使用Feign,请创建一个接口并对其进行注释。它具有可插入的注释支持,包括Feign注释和JAX-RS注释。Feign还支持可插拔编码器和解码器。Spring Cloud添加了对Spring MVC注释的支持,并支持使用HttpMessageConvertersSpring Web中默认使用的注释。Spring Cloud集成了Ribbon和Eureka以及Spring Cloud LoadBalancer,以在使用Feign时提供负载平衡的http客户端。
使编写Java Http客户端更加容易
使用 RestTemplate+Ribbon 时,利用 RestTemplate 对http 请求的封装处理,形成一套模板化的调用方法,但是在实际中,由于对服务的调用可能不止一处,往往一个接口会被多处调用,所以通常都会针对每个微服务自行封装一些客户端类来包装这些依赖服务的调用。所以Feign在此基础上做了进一步封装,由他来帮助我们定义和实现服务接口的定义。在Feign的实现下我们只需要创建一个接口并使用注解来配置它(以前是Dao接口上标注Mapper注解,现在是一个微服务接口上面标注一个Feign注解即可)。自动封装服务调用客户端的开发量。
Feign集成了Ribbon
利用Ribbon维护了Payment的服务列表信息,并且实现了轮询实现客户端的负载均衡。而与Ribbon不同的是,feign只需要定义服务绑定接口且以声明式的方法,优雅而简单的实现服务调用。
nginx+lua:是一个高性能的HTTP和反向代理服务器,lua是脚本语言,让Nginx执行Lua脚本,并且高并发、非阻塞的处理各种请求
springcloud gateway: Spring公司专门开发的网关,替代zuul
AlibabaCloud全家桶还没对应的网关,我们就用SpringCloud官方推荐的Gateway
OAuth是一个关于授权的开放网络标准,OAuth2是其2.0版本。
它规定了四种操作流程(授权模式)来确保安全
应用场景有第三方应用的接入、微服务鉴权互信、接入第三方平台、第一方密码登录等
OAuth2定义了四种授权模式(授权流程)来对资源的访问进行控制
无论哪个模式(流程)都拥有三个必要角色:客户端、授权服务器、资源服务器,有的还有用户(资源拥有者)
授权码模式是OAuth2目前最安全最复杂的授权流程
大致分为三大部分
这个客户端可以是浏览器,
- 客户端将client_id + client_secret + 授权模式标识(grant_type) + 回调地址(redirect_uri)拼成url访问授权服务器授权端点
- 授权服务器返回登录界面,要求用户登录(此时用户提交的密码等直接发到授权服务器,进行校验)
- 授权服务器返回授权审批界面,用户授权完成
- 授权服务器返回授权码到回调地址
客户端使用授权码换token
- 客户端接收到授权码,并使用授权码 + client_id + client_secret访问授权服务器颁发token端点
- 授权服务器校验通过,颁发token返回给客户端
- 客户端保存token到存储器(推荐cookie)
客户端使用token访问资源
- 客户端在请求头中添加token,访问资源服务器
- 资源服务器收到请求,先调用校验token的方法(可以是远程调用授权服务器校验端点,也可以直接访问授权存储器手动校对)
- 资源服务器校验成功,返回资源
整体上来说,可以用一句话概括授权码模式授权流程
客户端换取授权码,客户端使用授权码换token,客户端使用token访问资源
隐式授权模式大致可分为两部分:
- 客户端(浏览器或单页应用)将client_id + 授权模式标识(grant_type)+ 回调地址(redirect_uri)拼成url访问授权服务器授权端点
- 授权服务器跳转用户登录界面,用户登录
- 用户授权
- 授权服务器访问回调地址返回token给客户端
客户端使用token访问资源
- 客户端在请求头中添加token,访问资源服务器
- 资源服务器收到请求,先调用校验token的方法(可以是远程调用授权服务器校验端点,也可以直接访问授权存储器手动校对)
- 资源服务器校验成功,返回资源
用一句话概括隐式授权模式授权流程
客户端让用户登录授权服务器换token,客户端使用token访问资源
密码模式大体上也分为两部分:
用户在客户端提交账号密码换token
- 客户端要求用户登录
- 用户输入密码,客户端将表单中添加客户端的client_id + client_secret发送给授权服务器颁发token端点
- 授权服务器校验用户名、用户密码、client_id、client_secret,均通过返回token到客户端
- 客户端保存token
客户端使用token访问资源
- 客户端在请求头中添加token,访问资源服务器
- 资源服务器收到请求,先调用校验token的方法(可以是远程调用授权服务器校验端点,也可以直接访问授权存储器手动校对)
- 资源服务器校验成功,返回资源
一句话概括用户名密码模式流程:
用户在客户端提交账号密码换token,客户端使用token访问资源
客户端模式大体上分为两部分:
客户端使用自己的标识换token
- 客户端使用client_id + client_secret + 授权模式标识访问授权服务器的颁发token端点
- 授权服务器校验通过返回token给客户端
- 客户端保存token
客户端使用token访问资源
- 客户端在请求头中添加token,访问资源服务器
- 资源服务器收到请求,先调用校验token的方法(可以是远程调用授权服务器校验端点,也可以直接访问授权存储器手动校对)
- 资源服务器校验成功,返回资源
一句话概括客户端模式授权流程:
客户端使用自己的标识换token,客户端使用token访问资源