1.spingcloud用过哪些组件
注册中心 eureka,nacos(常用),Consul,zookeeper
远程调用 dubbo(常用,rpc调用),openfeign(RESTful API调用)
网关 gateway
微服务保护 sentinel(常用),Hystrix
分布式事务 seata
2.熔断器有哪些
sentinel,Hystrix,
3.限流的算法
计数器算法,漏桶算法,令牌桶算法
限流的算法有哪些? | Javaᶜⁿ 面试突击 (javacn.site)
4.网关怎么实现的
-
客户端请求进入网关后由
HandlerMapping
对请求做判断,找到与当前请求匹配的路由规则(Route
),然后将请求交给WebHandler
去处理。 -
WebHandler
则会加载当前路由下需要执行的过滤器链(Filter chain
),然后按照顺序逐一执行过滤器(后面称为Filter
)。 -
图中
Filter
被虚线分为左右两部分,是因为Filter
内部的逻辑分为pre
和post
两部分,分别会在请求路由到微服务之前和之后被执行。 -
只有所有
Filter
的pre
逻辑都依次顺序执行通过后,请求才会被路由到微服务。 -
微服务返回结果后,再倒序执行
Filter
的post
逻辑。 -
最终把响应结果返回。
5.网关除了Gateway还知道别的吗
Nginx
Nginx是一个高性能的HTTP和反向代理服务器。Nginx一方面可以做反向代理,另外一方面可以做静态资源服务器,接口使用Lua动态语言可以完成灵活的定制功能 。
Nginx 在启动后,会有一个 Master 进程和多个 Worker 进程,Master 进程和 Worker 进程之间是通过进程间通信进行交互的,如图所示。Worker 工作进程的阻塞点是在像 select()、epoll_wait() 等这样的 I/O 多路复用函数调用处,以等待发生数据可读 / 写事件。Nginx 采用了异步非阻塞的方式来处理请求,也就是说,Nginx 是可以同时处理成千上万个请求的。
Zuul
Zuul 是 Netflix 开源的一个API网关组件,它可以和 Eureka、Ribbon、Hystrix 等组件配合使用。社区活跃,融合于 SpringCloud 完整生态,是构建微服务体系前置网关服务的最佳选型之一。
Zuul 的核心是一系列的过滤器,这些过滤器可以完成以下功能:
- 统一鉴权 + 动态路由 + 负载均衡 + 压力测试
- 审查与监控: 与边缘位置追踪有意义的数据和统计结果,从而带来精确的生产视图。
- 多区域弹性: 跨越 AWS Region 进行请求路由,旨在实现 ELB(Elastic Load Balancing,弹性负载均衡)使用的多样化,以及让系统的边缘更贴近系统的使用者。
Zuul 目前有两个大的版本:Zuul1 和 Zuul2
Zuul1 是基于 Servlet 框架构建,如图所示,采用的是阻塞和多线程方式,即一个线程处理一次连接请求,这种方式在内部延迟严重、设备故障较多情况下会引起存活的连接增多和线程增加的情况发生。
Netflix 发布的 Zuul2 有重大的更新,它运行在异步和无阻塞框架上,每个 CPU 核一个线程,处理所有的请求和响应,请求和响应的生命周期是通过事件和回调来处理的,这种方式减少了线程数量,因此开销较小。
Gateway
Spring Cloud Gateway 是Spring Cloud的一个全新的API网关项目,目的是为了替换掉Zuul1,它基于Spring5.0 + SpringBoot2.0 + WebFlux(基于⾼性能的Reactor模式响应式通信框架Netty,异步⾮阻塞模型)等技术开发,性能⾼于Zuul,官⽅测试,Spring Cloud GateWay是Zuul的1.6倍 ,旨在为微服务架构提供⼀种简单有效的统⼀的API路由管理⽅式。
Spring Cloud Gateway可以与Spring Cloud Discovery Client(如Eureka)、Ribbon、Hystrix等组件配合使用,实现路由转发、负载均衡、熔断、鉴权、路径重写、⽇志监控等,并且Gateway还内置了限流过滤器,实现了限流的功能 。
Kong
Kong是一款基于OpenResty(Nginx + Lua模块)编写的高可用、易扩展的,由Mashape公司开源的API Gateway项目。Kong是基于NGINX和Apache Cassandra或PostgreSQL构建的 ,能提供易于使用的RESTful API来操作和配置API管理系统,所以它可以水平扩展多个Kong服务器,通过前置的负载均衡配置把请求均匀地分发到各个Server,来应对大批量的网络请求。
Kong主要有三个组件:
- Kong Server : 基于Nginx的服务器,用来接收API请求。
- Apache Cassandra/PostgreSQL : 用来存储操作数据。
- Kong dashboard: 官方推荐UI管理工具,也可以使用 restfull 方式管理admin api。
Kong采用插件机制进行功能定制,插件集(可以是0或N个)在API请求响应循环的生命周期中被执行。插件使用Lua编写,目前已有几个基础功能:HTTP基本认证、密钥认证、CORS(Cross-Origin Resource Sharing,跨域资源共享)、TCP、UDP、文件日志、API请求限流、请求转发以及Nginx监控。
Kong网关具有以下的特性:
- 可扩展性: 通过简单地添加更多的服务器,可以轻松地进行横向扩展,这意味着您的平台可以在一个较低负载的情况下处理任何请求;
- 模块化: 可以通过添加新的插件进行扩展,这些插件可以通过RESTful Admin API轻松配置;
- 在任何基础架构上运行: Kong网关可以在任何地方都能运行。您可以在云或内部网络环境中部署Kong,包括单个或多个数据中心设置,以及public,private 或invite-only APIs。
来源:5 种主流API网关技术选型,yyds! 上-阿里云开发者社区 (aliyun.com)
6.sql调优
对表结构进行优化,拆分大表
增加常用查询项的索引
对于经常一起查询项的字段按照最左匹配原则简历合适的联合索引
优化查询sql语句,增加函数索引
对于多表关联查询,更改为小表驱动大表
对于后端代码优化减少不必要的重复查询
集成redis 将高频的查询结果 放到redie中缓存
7.哪些情况不走索引
对于组合索引,不是使用组合索引最左边的字段,则不会使用索引
以%开头的like查询如 %abc ,无法使用索引;非%开头的like查询如 abc% ,相当于范围查询,会使 用索引
查询条件中列类型是字符串,没有使用引号,可能会因为类型不同发生隐式转换,使索引失效
判断索引列是否不等于某个值时
对索引列进行运算
查询条件使用 or 连接,也会导致索引失效
ps:违背最左原则导致索引失效的情况:
以abc三个字段建立联合索引,如果查询时如果查询项中缺乏字段a,则不走索引
如select 1 from 表名 where b = '1' and c = '2';
但是如果是
select 1 from 表名 were a = '1' and c = '2';
则查询a字段走索引,查询字段c时不走索引。
查询语句select 1 from 表名 were b = '1' and a = '2';
也是走索引的,因为MySQL 的查询优化器会自动调整 where 子句的条件顺序以使用适合的索引。
select 1 from 表名 were a = '1' and b > '2' and c = '3';
对于上面这种类型的sql语句;mysql会一直向右匹配直到遇到范围查询(>、<、between、like)就停止匹配(包括like '查询值%'这种)。在a、b走完索引后,c已经是无序了,所以c就没法走索引,优化器会认为还不如全表扫描c字段来的快。所以只使用了(a,b)两个索引,影响了执行效率。
这种通过修改索引顺序为 abc_index:(a,c,b)就可实现全走索引。
8.知道回表查询吗
如果要查的字段包含非索引字段,那么查询器在查询完索引项后会根据索引再去表中查询非索引项。
9.mysql事务隔离级别
- 读未提交(Read Uncommitted)
- 特点:最低的隔离级别,事务中的修改,即使未提交,也可以被其他事务读取到。
- 优点:并发性能最好,读取到的数据最新。
- 缺点:存在脏读(Dirty Read)问题,即读取到未提交的数据,可能导致数据不一致性。
- 读已提交(Read Committed)
- 特点:保证事务读取到的数据都是已经提交的,其他事务提交的数据对该事务可见。
- 优点:避免了脏读的问题。
- 缺点:存在不可重复读(Non-Repeatable Read)问题,即同一个事务中,不同时间读取到的数据可能不一样。
- 可重复读(Repeatable Read)
- 特点:保证同一个事务中,多次读取同一条记录时,读取到的数据都是一致的,MySQL 默认的事务隔离级别。
- 优点:避免了不可重复读的问题。
- 缺点:存在幻读(Phantom Read)问题,即在一个事务中,两次查询同一个范围的记录,但第二次查询却发现了新的记录。
- 串行化(Serializable)
- 特点:最高的隔离级别,将所有的事务串行执行,保证了数据的完全隔离。
- 优点:避免了幻读的问题。
- 缺点:并发性能最差,可能导致大量的锁等待和死锁。
10.mysql事务传播机制
PROPAGATION_REQUIRED 如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择。(mysql默认事务传播机制)
PROPAGATION_SUPPORTS 支持当前事务,如果当前没有事务,就以非事务方式执行。
PROPAGATION_MANDATORY 使用当前的事务,如果当前没有事务,就抛出异常。
PROPAGATION_REQUIRES_NEW 新建事务,如果当前存在事务,把当前事务挂起。
PROPAGATION_NOT_SUPPORTED 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
PROPAGATION_NEVER 以非事务方式执行,如果当前存在事务,则抛出异常。
PROPAGATION_NESTED 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。
11.常用的类
String,Hashmap,ArrayList,Interger,Long,Double,Flout,Char等;
12.float和double区别
精准度不同:float占4个字节,double占8个字节;(1byte(字节) = 8 bit(位))
有效数组个数不同:float只能提供6位有效数字,double可提供16位有效数字
13.为何JDK9要将String的底层实现由char[]改成byte[]?
14.String的最大数量
public int length() {
return value.length >> coder();
}
15.String类的常用方法
length();返回字符串的长度
charAt(值);从字符串中取出指定位置的字符
toCharArray();将字符串变成一个字符数组
indexOf(“字符”);查找一个指定的字符串是否存在,返回的是字符串的位置,如果不存在,则返回-1
lastIndexOf(“字符”);得到指定内容最后一次出现的下标
toUpperCase(); toLowerCase();字符串大小写的转换
split(“字符”);根据给定的正则表达式的匹配来拆分此字符串。形成一个新的String数组。
equals(Object anObject);判断值是否相等
trim();去掉字符串左右空格
substring(int beginIndex,int endIndex);截取字符串
equalsIgnoreCase(String);忽略大小写的比较两个字符串的值是否一模一样
contains(String);判断一个字符串里面是否包含指定的内容
startsWith(String);测试此字符串是否以指定的前缀开始
endsWith(String);测试此字符串是否以指定的后缀结束
repalceFirst(String,String);将第一次出现的某个内容替换成指定的内容
replaceAll(String,String);将某个内容全部替换成指定内容
replace(char oldChar,char newChar);新字符替换旧字符
16.String使用的hash算法
17.常用集合
ArrayList ,Vector,HashMap,Hashtable,HashSet,LinkedList,ConcurrentHashMap
18.set有哪些子类
HashSet,LinkedHashSet,SortedSet,TreeSet
HashSet 基于 HashMap 实现。放入HashSet中的元素实际上由HashMap的key来保存,而HashMap的 value则存储了一个静态的Object对象。
19.map有哪些子类
HashMap,HashTable,ConcurrentHashMap,LinkedHashMap,TreeMap
HashTable是线程安全的,Hashtable很多方法是同步方法,在单线程环境下它比HashMap要慢。
Jdk1.5提供了ConcurrentHashMap,它是 HashTable的替代。
LinkedHashMap继承于HashMap,是HashMap和LinkedList的融合体,具备两者的特性。每次put操作都会将entry插入到双向链表的尾部。
TreeMap是一个能比较元素大小的Map集合,会对传入的key进行了大小排序。可以使用元素的自然顺序,也可以使用集合中自定义的比较器来进行排序。
20.ArrayList是怎么扩容的
ArrayList扩容的本质就是计算出新的扩容数组的size后实例化,并将原有数组内容复制到新数组中去。
public boolean add(E e) {
//判断是否可以容纳e,若能,则直接添加在末尾;若不能,则进行扩容,然后再把e添加在末尾
ensureCapacityInternal(size + 1); // Increments modCount!!
//将e添加到数组末尾
elementData[size++] = e;
return true;
}
// 每次在add()一个元素时,arraylist都需要对这个list的容量进行一个判断。
//通过ensureCapacityInternal()方法确保当前ArrayList维护的数组具有存储新元素的能力,
//经过处理之后将元素存储在数组elementData的尾部
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
//如果传入的是个空数组则最小容量取默认容量与minCapacity之间的最大值
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// 若ArrayList已有的存储能力满足最低存储要求,则返回add直接添加元素;
//如果最低要求的存储能力>ArrayList已有的存储能力,这就表示ArrayList的存储能力不足,
//因此需要调用 grow();方法进行扩容
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private void grow(int minCapacity) {
// 获取elementData数组的内存空间长度
int oldCapacity = elementData.length;
// 扩容至原来的1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
//校验容量是否够
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
//若预设值大于默认的最大值,检查是否溢出
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// 调用Arrays.copyOf方法将elementData数组指向新的内存空间
//并将elementData的数据复制到新的内存空间
elementData = Arrays.copyOf(elementData, newCapacity);
}
21.Arraylist 与 LinkedList的区别
1. ArrayList基于动态数组实现;LinkedList基于链表实现。
2. 对于随机index访问的get和set方法,ArrayList的速度要优于LinkedList。因为ArrayList直接通过 数组下标直接找到元素;LinkedList要移动指针遍历每个元素直到找到为止。
3. 新增和删除元素,LinkedList的速度要优于ArrayList。因为ArrayList在新增和删除元素时,可能扩容和复制数组;LinkedList实例化对象需要时间外,只需要修改指针即可。
22.数组和链表的区别
23.线程安全的List有哪些
Vector:add方法加了synchronized。
CopyOnWriteArrayList:add方法加了锁lock且复制数组操作,从而不允许读。
synchronizedList:add方法中加了synchronized,加的是同步代码块比Vector的同步方法颗粒度更高。
读多写少的情况下,推荐使用CopyOnWriteArrayList方式。
读少写多的情况下,推荐使用Collections.synchronizedList()的方式。
24.线程安全的map有哪些
hashtable:put方法上加了synchronized
synchronizedMap:put方法中加了synchronized
ConcurrentHashMap: 在 JDK 1.7 时,使用的是分段锁也就是 Segment 来实现线程安全的。 然而它在 JDK 1.8 之后,使用的是 CAS + synchronized 或 CAS + volatile 来实现线程安全的。
ConcurrentHashMap如何实现线程安全? | Javaᶜⁿ 面试突击 (javacn.site)
ConcurrentSkipListMap:通过跳表来实现的高并发容器;
25.ConcurrentHashMap为什么是线程安全的
在 JDK 1.7 时,使用的是分段锁也就是 Segment 来实现线程安全的。 然而它在 JDK 1.8 之后,使用的是 CAS + synchronized 或 CAS + volatile 来实现线程安全的。
ConcurrentHashMap如何实现线程安全? | Javaᶜⁿ 面试突击 (javacn.site)
26.线程的创建方法
继承 Thread 类来创建线程
通过实现 Runnable 接口来创建线程
实现 Callable 接口接口来创建线程
使用线程池来创建线程。
27.线程池的创建方法
- 使用 ThreadPoolExecutor 类手动创建:通过 ThreadPoolExecutor 类的构造函数自定义线程池的参数,包括核心线程数、最大线程数、线程存活时间、任务队列等。
- 使用 Executors 类提供的工厂方法创建:通过 Executors 类提供的一些静态工厂方法创建线程池,例如 newFixedThreadPool、newSingleThreadExecutor、newCachedThreadPool 等。
- 使用 Spring 框架提供的 ThreadPoolTaskExecutor 类:在 Spring 框架中可以通过 ThreadPoolTaskExecutor 类来创建线程池。
28.线程池的参数
- corePoolSize:核心线程数。
- maximumPoolSize:最大线程数。
- keepAliveTime:空闲线程存活时间。
- TimeUnit:时间单位。
- BlockingQueue:线程池任务队列。
- ThreadFactory:创建线程的工厂。
- RejectedExecutionHandler:拒绝策略。
29.线程池的拒绝策略
- AbortPolicy:默认策略,直接抛出 RejectedExecutionException 异常。
- CallerRunsPolicy:由调用者线程执行任务。
- DiscardPolicy:默默地丢弃任务,没有任何异常抛出。
- DiscardOldestPolicy:尝试抛弃队列中最旧的任务,然后重新尝试提交当前任务。
29.分段锁
分段锁(Segmented Locking)是一种用于提高多线程程序性能的锁机制,通过将锁细分来减少竞争,从而在高并发环境中提高性能。
分段锁的工作原理
分段锁的基本思想是将锁分为多个段(Segment),每个段独立加锁,这样在并发环境下,不同的线程可以同时操作不同的段,从而减少锁竞争,提高并发访问率。相比于一个全局锁来说,分段锁提供了更细粒度的锁控制,允许更高的并发。
分段锁的优势
高并发性能:在多线程环境下,通过减少锁的竞争,提高了并发性能。
减少阻塞时间:线程操作不同的段时可以同时进行,从而减少了线程阻塞的时间。
分段锁的劣势
内存开销:每个段都有自己的锁,相比于单一锁,分段锁会有更多的内存开销。
实现复杂性:分段锁的实现比单一锁复杂,需要仔细设计段的数量和大小,以及如何映射键到特定的段上。
30.用过哪些队列
LinkedList:LinkedList是Java中常用的双向链表实现,它同时实现了List接口和Queue接口,因此可以被用作队列来进行元素的添加和移除操作。
Queue<String> queue = new LinkedList<>();
queue.offer("a"); // 入队
queue.offer("b");
String element = queue.poll(); // 出队
System.out.println(element); // 输出:a
在上面的示例中,我们使用LinkedList实现了一个队列,并通过offer()方法进行入队操作,通过poll()方法进行出队操作。
ArrayDeque:ArrayDeque是一种基于数组的双端队列实现,它同样实现了Queue接口,并且在尾部添加和移除元素的操作具有较低的时间复杂度。
Queue<Integer> queue = new ArrayDeque<>();
queue.offer(1); // 入队
queue.offer(2);
int element = queue.poll(); // 出队
System.out.println(element); // 输出:1
在这个示例中,我们使用ArrayDeque实现了一个整数队列,并进行了入队和出队的操作。
PriorityQueue:PriorityQueue是一个基于优先级堆的无界优先级队列实现,它可以确保每次出队的元素都是队列中优先级最高的元素。
Queue<Integer> queue = new PriorityQueue<>();
queue.offer(5); // 入队
queue.offer(3);
int element = queue.poll(); // 出队
System.out.println(element); // 输出:3
在上述示例中,我们使用PriorityQueue实现了一个整数队列,并通过offer()方法入队,通过poll()方法出队。需要注意的是,PriorityQueue会根据元素的自然顺序或者比较器来决定出队顺序。
ps:队列的应用场景
1.消息队列
用于实现系统间的异步通信,可以将消息发送到队列中,然后由消费者从队列中取出进行处理。
示例:使用RabbitMQ、Kafka等消息队列实现订单处理系统,将订单消息发送到队列中,后台系统从队列中消费并处理订单。
2.线程池任务调度
用于按照顺序执行任务,通常使用队列来存储待执行的任务。
示例:使用Java的Executor框架创建线程池,将需要执行的任务添加到线程池的任务队列中,线程池按照队列中的顺序依次执行任务。
3.缓存淘汰策略
用于限制缓存的大小,当缓存满时,通过队列中的先进先出规则来淘汰最早添加的元素。
示例:使用LRU(最近最少使用)缓存淘汰算法,将不经常访问的数据移出缓存,保留最近访问的数据在缓存中。
4.网络请求调度
用于处理请求队列,按照先到先处理的顺序处理请求,实现请求的有序处理。
示例:Web服务器接收到多个客户端请求时,将请求放入请求队列,然后按照队列中的顺序依次处理请求。
5.广度优先搜索(BFS)
用于解决图和树等数据结构的搜索问题,通过队列来实现搜索的层级遍历。
示例:在无权图中找到两个节点之间的最短路径,使用广度优先搜索算法,使用队列来实现节点的层级遍历。
这些只是队列应用的一小部分示例,队列作为一种重要的数据结构,在计算机科学中被广泛应用。具体的应用场景根据问题的需求和实际情况选择合适的方法和数据结构。