近期的一些JAVA面试题目的整理

写在前面:是一些近期自己面试的一些记录,答案的整理可能也不是很对,做一些记录顺便大家一起探讨一下面试该怎么回答,求轻喷。

Spring相关

1、IOC和AOP

  IOC的话是Spring创建一个容器,根据xml的配置或者注解的形势,去实例化Bean对象,然后再去获取对象之间的引用关系,去进行一个依赖的注入,通过反射的机制去实现一个对Bean的管理的这么一个容器。
  
  对于AOP的话,Spring在运行的时候会通过动态代理的方式去实现在本身代码的基础上去执行一些织入的代码。创建这个代理类的实例对象,在动态代理类里面引用真正自己写的类,所有的方法调用都是先走代理类,负责做代码上的增强。
  
  <动态代理补充,如果类实现了某个接口,aop会使用JDK动态代理,如果没有则会使用cglib动态代理。invoke(),getInstatnce()有关实现>
  
  <三级缓存(三个map),一级缓存singleObjects是用来存放完整的bean对象的,不支持循环依赖情况下,只有一级缓存生效,二三级缓存用不到;二三级缓存是为了解决循环依赖,主要是可以解决循环依赖对象需要提前被aop代理,以及如果没有循环依赖,早期的bean也不会真正暴露,不用提前执行代理过程,也不用重复执行代理过程。>

2、设计模式以及应用场景

策略模式:先写一个抽象类,抽象类里面定义实现的方法,在写实现类去继承这个抽象类,在写具体的业务逻辑。
单例模式:Spring中每个Bean走的都是单例模式,保证运行期间只有一个实例对象
动态代理:Spring的AOP
工厂模式:Spring通过工厂模式获取实例对象。
​
适配器模式原理以及应用:适配器模式(adapter pattern)是将一个类的接口转换成期待的类型,主要目的为兼容性,一个适配使得因接口不兼容而不能在一起工作的类能在一起工作,做法是将类自己的接口包裹在一个已存在的类中,它的别名为包装器,属于结构性模式。有类适配器,对象适配器,接口适配器;适配器模式中有三个角色,被适配者Adapted,适配器Adapter,以及最终要转换成的目标对象Target。
适用场景:
1 系统需要使用现有的类,但这些类的接口不符合系统需要
2 建立一个可以重复使用的类
​
过滤器和拦截器:
区别:
  ①拦截器是基于java的反射机制的,而过滤器是基于函数回调。
  ②拦截器不依赖与servlet容器,过滤器依赖与servlet容器。
  ③拦截器只能对action请求起作用,而过滤器则可以对几乎所有的请求起作用。
  ④拦截器可以访问action上下文、值栈里的对象,而过滤器不能访问。
  ⑤在action的生命周期中,拦截器可以多次被调用,而过滤器只能在容器初始化时被调用一次。
  6.拦截器可以获取IOC容器中的各个bean,而过滤器就不行,这点很重要,在拦截器里注入一个service,可以调用业务逻辑。

3、事务的隔离级别和传播机制

隔离级别:
  读未提交:查询时候不加锁,写的时候加S锁,事务结束释放
  读已提交:读的时候加S锁,之后释放,写的时候加X锁,事务结束释放
  可重复读<默认>:读的时候加S锁,事务结束后释放,写的时候加X锁,事务结束后释放
  串行化:写的时候整个表X锁,读的时候整个表X锁。
​
传播机制:
  REQUIED<默认>:没有事务就创建一个新事物,存在则加入该事务;
  REQUIED_NEWS:创建新事物,无论存不存在都创建新事物;
  NESTED:如果当前存在事务,嵌套事务内执行,没有则新建;
  SUPPORTS:支持当前事务,如果当前存在事务,就加入该事务,如果不存在则已非事务执行;
  MANDATORY:支持当前事务,如果当前存在事务,就加入该事务,如果不存在就抛出异常;
  NOT_SUPPORTS:以非事务方式执行,存在事务的话,就把事务挂起;
  NEVER:以非事务执行,如果当前存在事务,就抛出异常

4、Bean的生命周期

1、首先是一个容器的创建(beanFactory,DefaultListableBeanFactory),向Bean工厂中设置一些参数(BeanPostProcessor,Aware接口的子类)等属性;
2、加载解析bean对象,准备要创建的Bean对象的定义对象beanDefinition,(也就是xml或者注解的解析过程)
3、beanFactoryPostProcessor的处理,此处是拓展点,
4、BeanPostProcessor的注册功能,方便后续对bean对象完成具体的拓展功能;
5、通过反射的方式讲beanDefinition对象实际化成具体的bean对象,
6、bean对象的初始化过程(填充属性,调用aware子类的方法,调用BeanPostProcessor前置处理方法,调用init-method方法,调用BeanPostProcessor的后置处理方法)
7、生成完整的bean对象,通过getBean方法可以直接获取
8、销毁过程
​
白话版:
  第一步:创建bean的方法是doCreateBean(),需要在堆内存中开一块内存空间给这个对象,createBeanInstance()方法里面的逻辑大概就是采用反射生成实例对象,还未进行属性的填充;
  第二步:填充bean的成员属性,populateBean方法里面的逻辑大概就是对使用到了注入属性的注解进行注入,如果在注入的过程的对象还没生成,则会跑去生产要注入的对象;
  第三步:调用initializeBean方法初始化bean(1调用实现Aware接口的方法(invokeAwareMethods())->调用before方法->调用init-method方法->调用before方法);
  <invokeAwareMethods()实现了BeanNameAware,BeanClassLoaderAware,BeanFactoryAware三个Aware接口的话,会依次调用setBeanName(), setBeanClassLoader(), setBeanFactory()方法>

5、Spring、Spring Boot、Spring Cloud、Spring MVC

1、Spring和SpringBoot的区别:
  Spring Boot基本上是Spring框架的扩展,springboot通过starter这一个依赖,以简化构建和复杂的应用程序配置,尽可能的自动化配置Spring功能,Spring Boot只需要一个依赖项来启动和运行Web应用程序
​
2、SpringBoot自动配置的原理:
  @SpringBootConfiguration:标记当前类为配置类
  @EnableAutoConfiguration:开启自动配置
  @ComponentScan:扫描主类所在的同级包以及下级包里的Bean
  这个注解也是一个派生注解,其中的关键功能由@Import提供,其导入的AutoConfigurationImportSelector的selectImports()方法通过SpringFactoriesLoader.loadFactoryNames()扫描所有具有META-INF/spring.factories的jar包。spring-boot-autoconfigure-x.x.x.x.jar里就有一个这样的spring.factories文件。
  这个@EnableAutoConfiguration注解通过@SpringBootApplication被间接的标记在了Spring Boot的启动类上。在SpringApplication.run(...)的内部就会执行selectImports()方法,找到所有JavaConfig自动配置类的全限定名对应的class,然后将所有自动配置类加载到Spring容器中

6、Bean的线程安全保证

  对于prototype作用域的Bean,每次都创建一个新对象,也就是线程之间不存在Bean共享,因此不会有线程安全问题。
  对于singleton作用域的Bean,所有的线程都共享一个单例实例的Bean,因此是存在线程安全问题的。但是如果单例Bean是一个无状态Bean,也就是线程中的操作不会对Bean的成员执行查询以外的操作,那么这个单例Bean是线程安全的。比如Controller类、Service类和Dao等,这些Bean大多是无状态的,只关注于方法本身。
  <有状态Bean(Stateful Bean) :就是有实例变量的对象,可以保存数据,是非线程安全的。
​
无状态Bean(Stateless Bean):就是没有实例变量的对象,不能保存数据,是不变类,是线程安全的。>

7、源码相关

8、JWT、Spring Security

Spring Security:
  核心功能包括认证、授权、攻击保护;只需要重写 WebSecurityConfigurerAdapter 的方法,即可配置拦截什么URL、设置什么权限等安全控制。
  加密方式:BCryptPasswordEncoder,BCryptPasswordEncoder使用BCrypt强哈希函数;自定义加密,implements PasswordEncoder
​

JDK源码

1、map

HashMap:
  基于哈希表实现,底层结构JDK1.8以后是数组+链表+红黑树,数组的扩容类似于ArrarList的扩容,老数组和新数组
ConcurrentHashMap:
  1.7:ReentrantLock+Segment+HashEntry,一个Segment包含一个Hash分区,HashEntry是一个链表结构
  1.8:synchronized+CAS+Node+红黑树,查找,替换,赋值操作都使用CAS;锁:锁链表的head节点,扩容的时候阻塞所有的读写操作,并发扩容
  
HashTable:
  和HashMap类似,但是是线程安全的,方法有synchronized关键字,
LinkedHashMap:
  双向链表来维护元素的顺序,顺序为插入顺序
​
  concurrentHashMap 实现线程安全的底层原理是什么:
  JDK 1.7以及之前的版本里,分段加锁;1.8以后做了一些优化和改进,锁粒度的细化,一个大的数组,每个元素进行put操作,都是一个不同的锁,CAS策略  同一时间只有一个线程能成功执行
  
HashMap的遍历方式:
  forEach(key,value);
  
  Iterator it = map.entrySet().iterator();
        while (it.hasNext()) {
            Map.Entry entry = (Map.Entry) it.next();
            key = (String) entry.getKey();
            value = (String) entry.getValue();
            System.out.println("key:" + key + "---" + "value:" + value);
        };
        
   / 获取键集合的迭代器
        Iterator it = map.keySet().iterator();
        while (it.hasNext()) {
            key = (String) it.next();
            value = (String) map.get(key);
            System.out.println("key:" + key + "---" + "value:" + value);
        }

2、List

ArraryList:
  基于动态数组,连续内存存储,适合下标访问,扩容机制:老数组拷贝到新数组,使用尾插和指定初始容量可以极大的提升性能
LinkedList:
  基于链表,可以存在分散的内存中,适合插入以及删除操作,不适合查询,遍历必须使用迭代器iterator

JVM相关

1、JVM内存结构

方法区:共用,存放类的方法区
程序计数器:私有,记录当前执行的字节码指令的位置
虚拟机栈:私有,存放每个方法中的局部变量表
堆内存:共用,存放代码中创建的各种对象
字节码执行引擎:执行编译出的代码指令

2、堆内存结构

年轻代:
  Eden区+S1+S2
老年代:
​
年轻代和老年代的比例通常是1:2

3、垃圾回收算法

标记-清理:没用的对象标记出来,会产生内存碎片
标记-整理:有用的对象压缩到一起去,避免产生内存碎片
复制:内存一分为二,有用的拷贝到一起去,对象的移动和复制问题

4、垃圾回收器

serial-serial old:
parallel scavenge-parallel old:
parnew-cms:
​
G1:优先回收垃圾存放最多的分区,垃圾回收暂停时间短,同时也能维持较好的吞吐量
三色标记:黑色:自身和成员变量都标记完成;灰色:自身已标记,成员变量未标记完成;白色:自身和成员变量都没有标记

5、类加载机制

启动类加载器<-拓展类加载器<-应用程序加载器(双亲委派)

6、OOM异常的排查

首先,你必须先加上一些jvm的参数,让线上系统定期打出来gc的日志:
-XX:+PrintGCTimeStamps
-XX:+PrintGCDeatils
-Xloggc:<filename>
线上jvm必须配置-XX:+HeapDumpOnOutOfMemoryError,-XX:HeapDumpPath=/path/heap/dump。因为这样就是说OOM的时候自动导出一份内存快照,你就可以分析发生OOM时的内存快照了,到底是哪里出现的问题。

7、类加载进JVM过程

claas文件加载进来的类会存放在方法区,通过字节码执行引擎去执行字节码指令,会有程序计数器去记录指令位置,每个类都会有一个线程,线程会启动每个对应的JAVA虚拟机栈,用来保存每个方法内的局部变量,再将代码中创建的各种对象存放在JAVA堆内存中。

8、JVM调优

9、类加载到使用

加载:代码中用到这个类的时候
验证:验证是否符合指定规范
准备:给类分配一定的内存空间,然后类变量分配,默认初始值
解析:符号引用解析为直接引用
初始化:核心阶段,先初始化父类
使用:
卸载:

Mybatis

1、分页的实现

sql的解析是在StatementHandler里完成的,所以为了重写sql需要拦截StatementHandler。sql重写其实在原始的sql语句上加入分页的参数

2、一级缓存和二级缓存

一级缓存是基于sqlSession的,是默认的缓存,内部存储是HashMap,用来缓存查询对象;
二级缓存是基于Mapper级别,多SqlSession共享的,缓存的对象需要序列化,缓存的是数据不是对象;
二级缓存->一节缓存->查询数据库

MySQL

1、索引

索引分类:
  普通索引:这是最基本的索引,它没有任何限制,大多数情况下用到的索引。
  唯一索引:与普通索引类似,不同的就是:索引列的值必须唯一,但允许有空值(注意和主键不同)。如果是组合索引,则列值的组合必须唯一,创建方法和普通索引类似
  组合索引:是因为MySQL组合索引“最左前缀”的结果。简单的理解就是只从最左面的开始组合。并不是只要包含这两列的查询都会用到该组合索引
​
索引失效的原因:
  1.如果条件中有or;
  2.like查询是以%开头;
  3.使用函数的情况
  4.在条件中对索引值运算
​
索引的数据结构:
  B+Tree,
​
bTree和hash索引的区别:
从本质上理解,BTREE是一种有序树,而hash是无序的。所以最关键的区别在于:
1,BTREE可以用来做范围查询,比如大于,小于,而HASH索引仅在"=","IN"和"<=>"查询时效率较高;
2,HASH索引不能避免排序操作;(有order by的时候用btree
3, HASH索引不能用来做部分索引;(有组合索引的时候用btree)
4, 如果被索引列有很多相同值的时候,hash冲突会比较多,效率可能不如btree; 但是如果都是不同值的时候(唯一),btree因为要进行树遍历查询,而hash可以O(1)查询,则hash的效率要高很多;
​
B+树和B数的区别:
  1、B+树所有的关键字在叶子结点出现,
  2、B+树为所有的叶子节点增加了一个链指针,提高区间访问性能
  3、B+树的中间节点不保存数据,所以磁盘页能容纳更节点元素;
  4、B+树查询必须查找到到叶子节点,B树只要匹配到即可
解决引用函数索引失效的情况:
  

2、ACID以及实现

A原子性:事务执行过程中,要么全部成功,要么全部失败;数据库利用Innodb的undo log回滚日志,是实现原子性的关键。
​
C一致性:事务前后状态保持一致,不存在中间状态;
​
I隔离性:多个事务并发执行的时候,事务之间是相互隔离;数据库利用MVCC机制(多版本并发控制)保证隔离型,一个记录数据有多个版本快照数据,这些快照数据在undo log中。读已提交不满足隔离型.
​
D持久性:事务一旦提交,对数据的改变是永久的;数据库利用redo log,在内存中操作的同时,还会在redo log中记录这次操作,

3、回表查询

通俗的讲就是,如果索引的列在 select 所需获得的列中(因为在 mysql 中索引是根据索引列的值进行排序的,所以索引节点中存在该列中的部分值)或者根据一次索引查询就能获得记录就不需要回表,如果 select 所需获得列中有大量的非索引列,索引就需要到表中找到相应的列的信息,这就叫回表。
基于非主键索引的查询需要多扫描一棵索引树。因此,我们在应用中应该尽量使用主键查询。

4、MyISAM和InnoDb

MyISAN不支持事务,是非聚簇索引(索引和数据单独存储),查询性能更好;
InnoDb支持行锁和事务,是聚簇索引(叶子结点存数据);

5、MySQL和Pg的区别

一.PostgreSQL相对于MySQL的优势
1、在SQL的标准实现上要比MySQL完善,而且功能实现比较严谨;
2、存储过程的功能支持要比MySQL好,具备本地缓存执行计划的能力;
3、对表连接支持较完整,优化器的功能较完整,支持的索引类型很多,复杂查询能力较强;
4、PG主表采用堆表存放,MySQL采用索引组织表,能够支持比MySQL更大的数据量。
5、PG的主备复制属于物理复制,相对于MySQL基于binlog的逻辑复制,数据的一致性更加可靠,复制性能更高,对主机性能的影响也更小。
6、MySQL的存储引擎插件化机制,存在锁机制复杂影响并发的问题,而PG不存在。
​
二、MySQL相对于PG的优势:
1、innodb的基于回滚段实现的MVCC机制,相对PG新老数据一起存放的基于XID的MVCC机制,是占优的。新老数据一起存放,需要定时触 发VACUUM,会带来多余的IO和数据库对象加锁开销,引起数据库整体的并发能力下降。而且VACUUM清理不及时,还可能会引发数据膨胀;
2、MySQL采用索引组织表,这种存储方式非常适合基于主键匹配的查询、删改操作,但是对表结构设计存在约束;
3、MySQL的优化器较简单,系统表、运算符、数据类型的实现都很精简,非常适合简单的查询操作;
4、MySQL分区表的实现要优于PG的基于继承表的分区实现,主要体现在分区个数达到上千上万后的处理性能差异较大。
5、MySQL的存储引擎插件化机制,使得它的应用场景更加广泛,比如除了innodb适合事务处理场景外,myisam适合静态数据的查询场景。

7、MVCC的实现原理

MVCC为多版本并发控制,是InnoDb提高并发性能和并发控制解决读写冲突的一种手段,能够在非阻塞的情况下实现并发读;MVCC主要是基于UndoLog和ReadView实现,undolog用于数据的回滚和快照读的实现,ReadView是在读已提交和可重复读事务隔离级别下的快照读视图。

消息中间件

1、各个中间件的区别以及选型

MQ的使用场景:异步、削峰、解耦
​
RabbitMQ:订阅方式:点对点,广播(发布订阅)
RocketMQ:订阅方式:direct、topic、Headers和fanout
Kafka:订阅方式:基于topic/message以及按照消息类型、属性正则匹配的发布订阅方式;支持大量堆积;支持春旭消息
​
消息的顺序问题:
  1、从业务层面上去保证消息的顺序;
  2、保证生产者-MQServer-消费者是一对一的关系
消息的重复问题:
  1、消费端处理消息的业务逻辑保持幂等性,只要保持幂等性,最后处理结果都是一样的。保证每条消息都有唯一编号且保证消息处理成功与去重表的日志同时出现,利用一张日志表来记录已经处理成功的消息ID。
  2、保证消息的唯一性,写入消息队列的时候数据做唯一标识。消费消息时,根据唯一标识判断是否消费过。
  
消息可靠性:
  RabbitMQ提供transaction和confirm模式来确保生产者不丢失消息,
​
高可用:
  数据同步到所有节点,自动同步数据到所有节点,

2、在业务中的应用

1、Kafka配合websokcet去实现闸机指令的新增人员和删除人员,异步
2、Kafka和ES进行日志

3、Kafka配合ES的使用

把日志写入kafka 然后es去消费日志信息然后存入 因为es的写能力很差 大量日志会导致es压力过大

4、Kafka的偏移量和groupId

偏移量:
  Kafka的一个内部主题中“consumer_offsets”,该主题默认有50个分区,每个分区3个副本,分区数量有参数offset.topic.num.partition设置。通过消费者组ID的哈希值和该参数取模的方式来确定某个消费者组已消费的偏移量保存consumer_offsets主题的哪个分区中。
  
  Kafka消费者API提供两种方法用来查询偏移量。
  一个是committed(TopicPartition partition)方法,这个方法返回一个OffsetAndMetadata对象,通过这个对象可以获取指定分区已提交的偏移量;
  另外一个方法position(TopicPartition partition)返回的是下一次拉取位置。
  
  Kafka消费者还提供了重置消费偏移量的方法,seek(TopicPartition partition, long offset),该方法用于指定消费起始位置,另外还有seekToBeginning()和seekToEnd(),
  
  偏移量提交有自动和手动,默认是自动(enable.auto.commit = true)。自动提交的话每隔多久自动提交一次呢?这个由消费者协调器参数auto.commit.interval.ms 毫秒执行一次提交。有些场景我们需要手动提交偏移量,尤其是在一个长事务中并且保证消息不被重复消费以及消息不丢失。比如生产者一个订单提交消息,消费者拿到后要扣减库存,扣减成功后该消息才能提交,所以在这种场景下需要手动提交,因为库存扣减失败这个消息就不能消费,同时客户这个订单状态也不能是成功。
  
GroupId:
  只要不更改group.id,每次重新消费kafka,都是从上次消费结束的地方继续开始;
  只要group.id是全新的,就会从最新的的offset开始消费
  只要不更改消费组,只会从上次消费结束的地方继续消费

5、Kafka的分区

一个topic 可以配置几个partition,produce发送的消息分发到不同的partition中,consumer接受数据的时候是按照group来接受,kafka确保每个partition只能同一个group中的同一个consumer消费
​
kafka使用分区将topic的消息打散到多个分区分布保存在不同的broker上,实现了producer和consumer消息处理的高吞吐量。而consumer,同一个消费组内的所有consumer线程都被指定topic的某一个分区进行消费。
如果你的分区数是N,那么最好线程数也保持为N,这样通常能够达到最大的吞吐量。超过N的配置只是浪费系统资源,因为多出的线程不会被分配到任何分区。

6、Redis的集群

Redis集群是一个由多个主从节点群组成的分布式服务集群,它具有复制、高可用和分片特性。Redis集群不需要sentinel哨兵也能完成节点移除和故障转移的功能。需要将每个节点设置成集群模式,这种集群模式没有中心节点,可水平扩展,据官方文档称可以线性扩展到上万个节点(官方推荐不超过1000个节点)。redis集群的性能和高可用性均优于之前版本的哨兵模式,且集群配置非常简单。

7、Redis的持久化机制

什么是持久化:
  持久化就是把内存中的数据存放到磁盘中,防止宕机后内存数据丢失。
​
RDB:是Redis DataBase缩写快照
  RDB是Redis默认的持久化方式。按照指定的时间间隔内将内存的数据以快照的形式保存到硬盘中,对应产生的数据文件为dump.rdb。通过配置文件中的save参数来定义快照的周期。
  恢复时,将rdb文件直接读到内存中,具体做法:将rdb文件放到redis的启动目录上,redis会自动检查dump.rdb文件,恢复数据
​
AOF:AOF持久化(即Append Only File持久化),则是将Redis执行的每次写命令记录到日志文件中,当重启Redis会加载appendonly.aof文件来恢复数据。
  AOF是存放每条写命令的,所以会不断的增大,当大到一定程度时,AOF会做rewrite操作,rewrite操作就是基于当时redis的数据重新构造一个小的AOF文件,然后将大的AOF文件删除
  
优点:
(1)AOF日志文件适合做灾难性的误删除紧急恢复,如果某人不小心用flushall命令清空了所有数据,只要这个时候还没有执行rewrite,那么就可以将日志文件中的flushall删除,进行恢复。
(2)每进行一次 命令操作就记录到 aof 文件中一次 保证了数据的安全性和完整性
(3)AOF以append-only的模式写入,所以没有任何的磁盘寻址的开销,写入性能非常的高。
缺点:AOF 文件比 RDB 文件大,且恢复速度慢

8、mq确保生产者发送消息成功和消费者确保收到消息

ack机制:
  为了保证数据不被丢失,RabbitMQ支持消息确认机制,即ack。发送者为了保证消息肯定消费成功,只有使用方明确表示消费成功,RocketMQ才会认为消息消费成功。中途断电,抛出异常等都不会认为成功——即都会重新投递。
  保证数据能被正确处理而不仅仅是被Consumer收到,我们就不能采用no-ack或者auto-ack,我们需要手动ack(manual-ack)。在数据处理完成后手动发送ack,这个时候Server才将Message删除。

9、Redis分布式锁高并发情况是怎么保证高可靠性的

redis之所以能解决高并发的原因是它可以直接访问内存,而以往我们用的是数据库(硬盘),提高了访问效率,解决了数据库服务器压力。

10、Redis多线程的概念

Redis 6.0版本以后引入的多线程;
线程数一定要小于机器核数,尽量不超过8个;
​
Redis 的多线程部分只是用来处理网络数据的读写和协议解析,执行命令仍然是单线程顺序执行。
​

微服务

1、对微服务的理解,和单体的区别

微服务:
​
单体:
随着业务逐渐发展,产品思想会变得越来越复杂,单体结构的应用也会越来越复杂。这就会给应用带来如下的几个问题:开发效率变低:开发人员同时开发一套代码,很难避免代码冲突。开发过程会伴随着不断解决冲突的过程,这会严重的影响开发效率;排查解决问题成本高:线上业务发现 bug,修复 bug 的过程可能很简单。但是,由于只有一套代码,需要重新编译、打包、上线,成本很高。由于单体结构的应用随着系统复杂度的增高,会暴露出各种各样的问题。
​

2、SpringCloud相关组件

Spring Cloud Config:
  配置中心,用来管理配置
Spring Cloud Bus:
​
Spring Cloud gateway:
  实现:通过拦截到Path,然后去匹配配置好的uri进行一个请求的转发;chain.fliter去传递到下一个fliter,通过getorder()去调优先级,越小优先级越高.
  Ribbon:相同服务来选取,服务名做负载
  可配置权重:通过Weight中的两个参数,配置不同大小的数字<eg:一个5,一个95>来进行权重的分配,实现原理是底层数组去实现的.灰度发布也可以这样
  gateway也可以做限流,通过配置filters-args去做一个限流的配置,限流的实现是一个内置令牌+redis去实现的,可以配置一个发令牌速度和总令牌数(redis自身有超时机制去清理令牌),可以限制个数也可以限制网速
Spring Cloud Feign/openFeign:
  起到服务和服务之间的一个调用;openFeign可以用Spring MVC的注解
​
Spring Cloud Eureka:
  服务的注册与发现:
    Eureka 主管服务注册与发现,在微服务中,以后了这两者,只需要使用服务的标识符(==就是那个在每个服务的yml文件中取得服务名称==),就可以访问到服务,不需要修改服务调用的配置文件。
    三大角色: 
      Eureka Server:提供服务的注册与发现
      Service Provider: 服务提供方,将自身服务注册到Eureka,从而使服务消费能够找到
      Service Consumer: 服务消费方,从Eureka获取注册服务列表,从而能够消费服务
​
Spring CLoud Nacos:
  注册中心和配置中心
  
Spring Cloud Hystrix: 
  Hystrix是一个延迟和容错库,旨在隔离远程系统、服务和第三方库,阻止级联故障,在复杂系统中实现恢复能力。
  隔离:将请求封装在HystrixCommand中,然后这些请求在一个独立的线程中执行,每个依赖服务维护一个小的线程池(或信号量),在调用失败或超时的情况下可以断开依赖调用或者返回指定逻辑
  熔断:当HystrixCommand请求后端服务失败数量超过一定比例(默认50%), 断路器会切换到开路状态(Open). 这时所有请求会直接失败而不会发送到后端服务,断路器保持在开路状态一段时间后(默认5秒),自动切换到半开路状态(HALF-OPEN),这时会判断下一次请求的返回情况, 如果请求成功, 断路器切回闭路状态(CLOSED),否则重新切换到开路状态(OPEN)
  降级:服务降级是指当请求后端服务出现异常的时候, 可以使用fallback方法返回的值,兜底数据
​

3、分布式事务,实现过程以及为什么使用

SEATA的AT模式:
  执行阶段:
    在执行阶段Seata 的 JDBC 数据源代理通过对业务 SQL 的解析,把业务数据在更新前后的数据镜像组织成回滚日志,利用 本地事务 的 ACID 特性,将业务数据的更新和回滚日志的写入在同一个 本地事务 中提交。这样,可以保证:任何提交的业务数据的更新一定有相应的回滚日志存在。基于这样的机制,分支的本地事务便可以在全局事务的 执行阶段 提交,马上释放本地事务锁定的资源。
  完成阶段:
    如果决议是全局提交,此时分支事务此时已经完成提交,不需要同步协调处理(只需要异步清理回滚日志),完成阶段 可以非常快速地结束。如果决议是全局回滚,RM 收到协调器发来的回滚请求,通过 XID 和 Branch ID 找到相应的回滚日志记录,通过回滚记录生成反向的更新 SQL 并执行,以完成分支的回滚。
TCC:
2PC:
3PC:

4、分布式ID

1、雪花算法:
2、自增ID:

5、Feign、Gateway、Hystrix之间的使用

Feign的使用的话是在feign服务当提供需要使用的微服务的接口,通过@FeignClient注解配置的value值和接口url去进行一个匹配;

6、为什么用feign和eureka

Feign是用@FeignClient来映射服务的。
Feign是一个声明式的Web Service客户端,它的目的就是让Web Service调用更加简单。Feign提供了HTTP请求的模板,通过编写简单的接口和插入注解,就可以定义好HTTP请求的参数、格式、地址等信息。
Feign则会完全代理HTTP请求,我们只需要像调用方法一样调用它就可以完成服务请求及相关处理。Feign整合了Ribbon和Hystrix(关于Hystrix我们后面再讲),可以让我们不再需要显式地使用这两个组件

多线程

1、线程池的参数

ThreadPoolExecutor():有七个参数,核心线程数,最大线程数,生存时间,生存时间单位,任务队列,线程工场,拒绝策略;
​
具体流程:当核心线程数的线程都在处理任务,再请求进来的任务会进任务队列,当任务队列满了以后会启动新线程,达到最大线程数时候,再有任务进来会启动拒绝策略;
​
手写拒绝策略:自定义策略,在策略中讲任务放进消息中间件或者数据库中,监听线程池空闲线程时候,再去实行消费;
​
任务队列:ArrayBlockingQueue 固定长度;
线程工厂:产生线程的方式,指定group name->方便出错回溯,找到出错的线程池,设定非守护线程,优先级
​
自定义线程池:自定义group name,方便出错回溯,自定义任务队列,担心无界队列会带来的内存溢出的问题;拒绝策略中,自定义拒绝策略,在消息中间件或者mysql中去存储信息,等有线程空闲的时候再拿出来消费,如果一直没消费,考虑水平扩展服务器;
​
JDK默认实现的线程池:
    singleThreadExecutor():单线程的线程池,保证任务执行顺序;
    newcacheThreadPool():无界队列,
    FixedThreadPool(): 核心和最大线程固定,无界队列;
    ScheuledThreadPool(): 定时线程池,延迟队列

2、项目中的实际应用

1、异步处理数据
2、处理后台任务

3、启动线程的四种方式

1、实现Runnable,无返回值
2、实现Callable,有返回值
3、继承Thread类创建多线程
4、创建线程池来创建线程

4、线程个数的配置

压测再计算

5、Synchronized关键字

原理:jvm指令和monitor有关,Enter加锁 exit释放锁
​
synchronized和ReentrantLock的区别:
  1、synchronized 是JVM层面的锁,是Java关键字,通过monitor对象来完成(monitorenter与monitorexit),对象只有在同步块或同步方法中才能调用wait/notify方法,ReentrantLock 是从jdk1.5以来(java.util.concurrent.locks.Lock)提供的API层面的锁。
  2、实现方面来说,synchronized 的实现涉及到锁的升级,具体为无锁、偏向锁、自旋锁、向OS申请重量级锁,ReentrantLock实现则是通过利用CAS(CompareAndSwap)自旋机制保证线程操作的原子性和volatile保证数据可见性以实现锁的功能。
  3、 synchronized是自动加锁解锁,ReentrantLock是手动加锁解锁的
  
锁升级:
  1、没有多线程竞争,线程锁为偏向锁;
  2、当两个线程存在竞争关系时,第一个线程拥有偏向锁时,第二个线程会去撤销线程一的锁,撤销失败,线程二会进行一个锁的升级,升级到轻量级锁;
  3、自旋失败,锁膨胀升级为重量级锁;
​
CAS和Synchronized的区别:
  synchronized是悲观锁,会导致未获得锁的线程阻塞,等待持有锁的线程释放锁;CAS是一种乐观锁,多个线程执行某个任务,不会有冲突,有冲突会回滚,自旋锁,直到成功;场景使用不同,synchronized适用于并发比较高的情况,cas常用在并发比较低的情况下
  
Synchronized和Lock的异同:
  Synchronized是自动加锁自动解锁的,有一个锁的升级;
  Lock是手动加锁手动解锁的;

7、悲观锁和乐观锁

悲观锁:每次操作前都会进行上锁,行锁、表锁、读锁、写锁
乐观锁:每次拿数据认为别人不会修改,所以不会上锁,但是在更新的时候会去判断一下在此期间别人有没有更新这个数据,版本号等机制;
读锁: 也叫做S锁,shared Lock,共享锁;加了S锁以后不可以加X锁,select * from lock in shared mode
写锁:也叫做X锁,Exclusive Lock,排它锁,加了X锁,其他对象拿不到任何锁。select * from XXX for update
死锁:先读再写,读锁可以重复加,写不了
​
加锁原则:加对共享性影响最小的锁,最小知道原则,最大限制保证被操作对象的并发性能。

8、产生死锁的的四个条件以及破坏

互斥条件:互相占用资源,且等待对方
    解决:使用乐观锁解决
不剥夺条件:拿了锁,别人没办法放开
    解决:申请失败的话,主动让出资源
请求和保持条件:已经有资源并且还继续请求资源
    只请求不保持:要请求先放手
    只保持不请求:都有了之后,不再请求
循环等待条件:环状
    解决:按顺序来请求资源

数据结构以及算法

1、栈和队列

栈的插入和删除操作都是在一端进行的,而队列的操作却是在两端进行的。
栈是先进后出,队列是先进先出。
栈只允许在表尾一端进行插入和删除,队列只允许在表尾一端进行插入,在表头一端进行删除。

2、树

平衡二叉树:
  AVL树是高度平衡的二叉树。它的特点是: AVL树中任何节点的两个子树的高度最大差别为1。
  实现:插入的时候max()方法比较大小、旋转
​
红黑树:
  红黑树(Red Black Tree) 是一种自平衡二叉查找树,是在计算机科学中用到的一种数据结构,典型的用途是实现关联数组,是平衡二叉树和AVL树的折中
​
其实平衡二叉树最大的作用就是查找,AVL树的查找、插入和删除在平均和最坏情况下都是O(logn)。AVL树的效率就是高在这个地方。如果在AVL树中插入或删除节点后,使得高度之差大于1。此时,AVL树的平衡状态就被破坏,它就不再是一棵二叉树;为了让它重新维持在一个平衡状态,就需要对其进行旋转处理, 那么创建一颗平衡二叉树的成本其实不小. 
​
红黑树与AVL树的比较: 1.AVL树的时间复杂度虽然优于红黑树,但是对于现在的计算机,cpu太快,可以忽略性能差异 2.红黑树的插入删除比AVL树更便于控制操作 3.红黑树整体性能略优于AVL树(红黑树旋转情况少于AVL树)

3、快速排序的实现

基本思想是: 选择一个基准数,通过一趟排序将要排序的数据分割成独立的两部分;其中一部分的所有数据都比另外一部分的所有数据都要小。然后,再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。

4、冒泡排序和选择排序

冒泡排序:
  它会遍历若干次要排序的数列,每次遍历时,它都会从前往后依次的比较相邻两个数的大小;如果前者比后者大,则交换它们的位置。这样,一次遍历之后,最大的元素就在数列的末尾! 采用相同的方法再次遍历时,第二大的元素就被排列在最大元素之前。重复此操作,直到整个数列都有序为止!
​
选择排序:
  基本思想是: 首先在未排序的数列中找到最小(or最大)元素,然后将其存放到数列的起始位置;接着,再从剩余未排序的元素中继续寻找最小(or最大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。 
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值