一:实际面试题
1.Nacos和Eureka的区别
1.Eureka通过yml配置注册地址,而nacos直接下载jar包启动.\startup.cmd -m standalone
2.高可用,eureka在yml文件中配置,nacos在conf文件中配置
3.Eureka需要配合mq动态刷新配置,而nacos保持长链接实时推送配置
4.Nacos可用根据业务和环境进行分组管理
5.Nacos支持在线服务管理,而eureka只能预览服务状态
6.Nacos端口8848 . Eureka端口80/8081
7.都支持AP模式,保证服务的高可用,底层都默认实现Ribbon负载均衡
特性 | Eureka | ZooKeeper | Nacos |
开发公司 | Netflix | Apache 基金会 | 阿里巴巴 |
CAP | AP(可用性和分区容忍性) | CP(一致性和分区容忍性) | 既支持AP,也支持CP |
功能 | 服务注册与发现 | 分布式协调、配置管理、分布式锁 | 服务注册与发现、配置管理、服务管理 |
定位 | 适用于构建基于 HTTP的微服务架构 | 通用的分布式协调服务框架 | 适用于微服务和云原生应用 |
访问协议 | HTTP | TCP | HTTP/DNS |
自我保护 | 支持 | - | 支持 |
数据存储 | 多个实内嵌数据库、多个实例形成集群 | ACID 特性的分布式文件系统 ZAB 协议 | 内嵌数据库、MySQL等 |
健康检查 | Client Beat | Keep Alive | TCP/HTTP/MYSQL/Cli |
2. 什么是AOP
主要是以一种编程思想,再不改变源代码的基础上对代码进行功能拓展,目的是为了降低代码间的耦合性
五大通知类型:Before Around After AfterReturn AfterThrow
3. 什么是Spring
核心思想IOC和AOP
是一款轻量级的IOC容器框架,将创建对象的权利和管理对象的生命周期交给IOC容器处理,DI是动态的将某种依赖关系注入到各个组件中,目的是为了提高代码间的松耦合
AOP:基于动态代理CGLIB完成———注解Before(后面跟要切入的方法) 类上注解Aspect表名切面
4. Linux怎么启动SpringBoot项目
mvn package进行打包
nohup Java –jar xxx.jar & ---后台运行
5. 怎么优化数据库
1.尽可能缩短字段长度
2.建立常用查询字段优化索引
3.使用缓存将常用查询数据且不会随意变化的数据放入缓存
4.主从分离读写,数据读操作和写操作分开
6. IOC底层原理实现
1.xml配置文件class类名
2.创建工厂类使用dom4j解析配置文件
3.使用反射获取对象返回一个实例
7. 讲一下GateWay
GateWay是一个微服务架构的API网关,作为接口服务请求的接入点实现安全验证、路由、过滤、流控。主要是三个配置:
Route路由:(一个url对应一个请求一个断言和一个过滤器)
设置断言(谓词:匹配url、请求方式、等)
定义过滤器:(Globalffilter(作用于所有路由)和GateWayfilter(作用于单个/一组路由)两种)
8. ==和equals的区别
==如果是比较基本类型比较的是值,如果比较的是引用类型,比较的是地址值
Equals一般比较的都是地址值,String一般重写后都是比较值是否相同
9. 高并发问题解决方案
熔断、限流、降级、负载均衡、缓存、异步、分库分表、消息队列RocketMq削峰
悲观锁:在最开始查询库存信息的时候加入for update 谁抢到锁谁就去执行后续业务,其他线程只能等待锁被释放,问题:性能底下,担心其他线程会操作数据,适合写入
乐观锁:使用时间戳限制重入的乐观锁。实现抢购商品设定100ms,循环获取判断请求时间是否大于100ms,大于就下单失败,小于就再次重试, 使用限定次数重入的乐观锁一般是在version中判断是否一致,一致就执行后续业务并返回,不一致就下次循环,重新获取库存信息, 不担心其他线程操作数据,但每次操作前都会核查数据是否被修改,适合查询
Redis乐观锁:watch监控key值对应的value值,如果不一致返回失败
10. 单体架构和分布式架构
单体架构:所有模块都耦合在一起,无法对特定模块进行优化,每次上线都需要全部编译,费事
分布式架构:不同的模块分为不同的工程,更新只要编译上线的代码块,扩展性高,耦合性低
11. 什么是反射
反射就是在运行状态中获取任意类的名称、属性、方法等
通过反射可以实现动态装配、降低代码的耦合性
12. JVM类加载流程
加载: 通过类全路径加载到JVM中,生成CLASS对象
验证: 字节码格式验证,如对jvm是否有害,是否符合当前虚拟机的要求 。
准备: 为类的静态变量分配内存并根据这个静态变量的数据类型进行初始化。
解析: 将符号引用替换成直接引用
初始化: 执行java代码
13. Mysql引擎有哪些
InnoDB和MyISAM
Innodb是Mysql默认存储引擎,支持事务、外键关联,行级锁,实现了缓冲管理,不仅能缓冲索引也能缓冲数据,并且会自动创建散列索引以加快数据的获取
两者的区别:innodb支持事务,适合频繁修改,行级锁,支持外键
MyISAM不支持事务,不支持外键,全表锁,适合查询
可以通过show engines查看搜索引擎
行锁和表锁:
行锁针对某一个主键索引,表锁针对全表
什么时候innodb行锁会升级成表锁及原因:查询数据时没有正确使用索引
1. 未使用索引
2. 索引失效:like语句%在做表也会导致索引失效
3. 索引字段值重复率过高:比如说状态值只有01
14. Token怎么传递
Cookie,session,消息头
15. Ribbon负载均衡底层原理
ClientHttpRequestInterceptor拦截器,被LoadBalancerInterceptor实现
16. 怎么实现多人同时登陆或单人登录
使用锁判断token是否存在,是就抛异常不能在登录。
多人登录是默认存在的
17. 查询重复数据
select * from people where peopleId in (select peopleId from people group by peopleId having count(peopleId) > 1)
18. 字节流和字符流的区别
字节流:直接操作磁盘中的文件,输入流和输出流父类InputStream和OutputStream,主要处理二进制数据,不必关闭文件流就会将数据传输至文件
字符流:在操作时通过了缓存区,顶级父类是Reader和Writer,通过将数据写入缓存区,只有flush或者关闭输入流时才会将数据放入磁盘文件中,操作的是2个字节的unicode字符
19. char和varchar的区别
Char定长,自动空格填充。sql优化时固定字符串长度可使用char字符,更节省空间
Varchar变长,长度可变。sql优化时varchar长度尽量设置最小使用长度,尽量不要有多余
20. List和Set区别
都继承至collection接口
1.List有序可重复,set无序不可重复
2.List自动增长,查询更高,set插入删除效率更高
21. @Autowired和@Resource注解的区别
1.@Autowired由Spring提供,默认按照ByType注入
2.@Resource由J2EE提供,默认按照ByName注入
22. Get和Post区别
1.Post隐藏提交数据,不会拼接到url地址栏,保密性更高,安全性更强,传输占用资源更少,传输效率更高
2.Get:自动拼接数据到url地址栏,安全性差,传输占用资源多,效率低
3.Get一般用来请求数据,post一般用来提交数据
Get数据一般在请求行通过对应对的形参名称接受,不一致就用Requestparm接受,在请求头用Requestbody接受
23. 深拷贝和浅拷贝的区别
深拷贝:拷贝过后,拷贝前的数据更改不会影响拷贝后的数据
浅拷贝:拷贝过后,拷贝后的数据会随着拷贝前的数据改变而改变
比如:A数据拷贝为B数据,A改变,B改变为浅拷贝,B不改变为深拷贝
24. 支付功能是怎么实现的
使用公司提供的唯一标示ID,当用户提交订单过后,生成一个URL,用户通过URL去支付,支付是否完成会有支付软件返回一个支付信息,判断返回的支付结果,改变订单状态
25. JDK新特性
Lomda表达式、函数式编程
26. 堆栈溢出
堆:完全二叉树树结构
栈:先进后出
代码会被装入到内存中的代码区
数据区又由 3 部分组成 :
变量:根据其是否有初始值,被装入到内存中的未初始化数据区和初始化数据区;
局部变量:在函数调用发生时存放在栈中;
动态内存空间:在程序运行时申请的动态内存空间存放在堆中
问题分析:
1. 堆栈尺寸小:-Xms<size>:用于设置jvm堆内存的初始大小
27. Java中String、StringBuffer和StringBuilder的区别和堆栈内存分配
1.栈:存储了指向所有对象的名称,指向堆内存
2.堆:对象
3.String str=new String("hello") 堆栈分别存什么
栈:存指向’hello’的地址值
堆:存有’hello’的空间和new之后开辟的’hello’第二个空间,
4.String、StringBuffer和StringBuilder的区别
1. String、StringBuffer、StringBuilder都是被final修饰的,是不能够被继承改写的。
2. String实例空间固定,String使用+拼接会开辟新的空间
3. StringBuffer线程安全+同步的可变序列,使用append拼接不会创建新空间
4. Stringbuilder线程不同步的可变序列,更高效
28. 实例方法和静态方法区别
实例方法可以调静态成员和实例成员
但静态方法只能访问静态变量和静态方法
调用静态方法不需要创建对象
29. Java中异常有哪几类?分别怎么使用?
Throwable异常顶级父类:包含了异常(Exception)和错误(Error)
异常包含:Runtime和IOEexception:运行时异常和非运行时异常
Error:一般由于程序无法处理
异常处理:非运行时异常可以通过捕获抛出,运行时异常应尽量避免,实在需要解决在处理为想要的结果(try … cath)
30. 常用集合类及List排序
Connection和Map顶级父类
Connection被List和Set继承 Map被 AbstractMap继承
List:有序可重复的集合, arraylist和LinkedList,sort()排序
ArrayList:
底层为Object类型的数组结构,可以为Null,有序重复
非线程安全,可通过加锁:Collections.synchronizedList(List list)返回安全List
LinkedList:底层为双向链表结构
Set:hashSet和treeSet,无重复数据,通过HashCode和equals判断重复
HashSet:线程不安全,底层是hash表
TreeSet:底层红黑树,默认自然排序
31. HashCode方法的作用
主要用来查找kv结构中的确定对象的存储地址,一般和equals一起重写
32. 如何实现安全线程的HashMap
1. 为什么HashMap线程不安全?
1.1 内部存储结构:HashMap内部存储使用了一个Node数组(默认大小是16),如果存在相同的HashCode和相同的key的元素,那么新值覆盖原来的旧值; 如果存在相同的HashCode,那么他们的索引位置就相同,这时判断他们的key是否相同,如果不相同,这时就是产生了hash冲突,这时数据放在一个 Entry 链。
1.2 自动扩容机制:如果多个线程同时检测到元素的个数超过阀值(数组大小*负载因子),多个进程会同时对Node数组进行扩容,都在重新计算元素位置以及复制数据,但是最终只有一个线程扩容后的数组会赋给table,其他线程的都会丢失,并且各自线程put的数据也丢失。
总结: 相同key不同value的,如果HashCode索引不一致会导致形成entry链
自动扩容时会开辟新的空间复制所有数据进去
解决方案:new ConcurrentHashMap或者Collections.synchronizedMap
33. 动态代理实现方式
Jdk原生动态代理和CGLIB动态代理
JDK代理:利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。---无需外部依赖,只能基于接口实现代理
public class DebugInvocationHandler implements InvocationHandler {
/**
* 代理类中的真实对象
*/
private final Object target;
public DebugInvocationHandler(Object target) {
this.target = target;
}
public Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {
//调用方法之前,我们可以添加自己的操作
System.out.println("before method " + method.getName());
Object result = method.invoke(target, args);
//调用方法之后,我们同样可以添加自己的操作
System.out.println("after method " + method.getName());
return result;
}
}
public static Object getProxy(Object target) {
ClassLoader classLoader = target.getClass().getClassLoader();
Class<?>[] interfaces = target.getClass().getInterfaces();
return Proxy.newProxyInstance(classLoader
, // 目标类的类加载
interfaces, // 代理需要实现的接口,可指定多个
new DebugInvocationHandler(target) // 代理对象对应的自定义 InvocationHandler
);
}
public static void main(String[] args) {
JdkSmsServiceImpl jdkSmsService = new JdkSmsServiceImpl();
JdkSmsService proxy = (JdkSmsService)getProxy(jdkSmsService);
proxy.send("id");
}
CGLIB代理:针对类实现代理,对指定的类生成一个子类,并覆盖其中的方法,这种通过继承类的实现方式,不能代理final修饰的类
public class MethodInterceptorTest implements MethodInterceptor {
/**
* 代理类中的真实对象
*/
private final Object target;
public MethodInterceptorTest(Object target) {
this.target = target;
}
@Autowired
private TransactionTemplate transactionTemplate;
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
//调用方法之前,我们可以添加自己的操作
System.out.println("before method " + method.getName());
Object result = method.invoke(target, args);
//调用方法之后,我们同样可以添加自己的操作
System.out.println("after method " + method.getName());
return result;
}
}
public static Object getProxy(Object target) {
return Enhancer.create(target.getClass(), new MethodInterceptorTest(target));
}
public static void main(String[] args) {
SmsServiceImpl smsService = new SmsServiceImpl();
SmsServiceImpl proxy = (SmsServiceImpl)getProxy(smsService);
proxy.send("id","name");
EmailServiceImpl emailService = new EmailServiceImpl();
EmailServiceImpl emailProxy = (EmailServiceImpl)getProxy(emailService);
emailProxy.send("id","name");
}
34. redis集群
单节点:就一个
主从模式:主节点master负责写入,从节点负责读取。主节点挂了不能重新选择新的主节点。每个节点之间关联关系小,只负责数据同步,一个节点挂了其他节点正常工作
哨兵模式:和主从区别在于主节点挂了后会从从节点自动选择新的主节点
35. RPC原理
1.服务消费者(client客户端)通过调用本地服务的方式调用需要消费的服务;
2.客户端存根(client stub)接收到调用请求后负责将方法、入参等信息序列化(组装)成能够进行网络传输的消息体;
3.客户端存根(client stub)找到远程的服务地址,并且将消息通过网络发送给服务端;
4.服务端存根(server stub)收到消息后进行解码(反序列化操作);
5.服务端存根(server stub)根据解码结果调用本地的服务进行相关处理;
6.本地服务执行具体业务逻辑并将处理结果返回给服务端存根(server stub);
7.服务端存根(server stub)将返回结果重新打包成消息(序列化)并通过网络发送至消费方;
8.客户端存根(client stub)接收到消息,并进行解码(反序列化);
9.服务消费方得到最终结果;
36. NIO是什么和BIO区别
NIO是非阻塞IO,BIO是阻塞IO
所谓阻塞是因为每次请求都会有新的线程处理,当线程数量达到上限时,无法处理新的请求,会直接拒绝处理这时前端会得到提示请稍后再试,nio是指先接受请求,返回前端请等待,后台将请求放入通道,慢慢进行io操作
NIO核心概念主要为:
1. Channel通道
1.1 FileChannel:文件通道用于从文件读取数据
RandomAccessFile accessFile =new RandomAccessFile("D:\\readerTest.txt", "rw");
FileChannel channel = accessFile.getChannel();
1.2 DatagramChannel:数据通道可以通过UDP(用户数据报协议)通过网络读取和写入数据
DatagramChannel channel = DatagramChannel.open();
1.3 SocketChannel:数据通道可以通过TCP(传输控制协议)通过网络读取和写入数据
//远程连接的两种方式 方式1
SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("www.baidu.com", 80));
//方式2
SocketChannel connectionOpen = SocketChannel.open();
connectionOpen.connect(new InetSocketAddress("www.baidu.com", 80));
1.4 ServerSocketChannel:允许用户监听传入的TCP连接,与Web服务器相同
int port =8888;
ByteBuffer buffer = ByteBuffer.wrap("asdawads".getBytes());
//绑定端口
ServerSocketChannel ssc = ServerSocketChannel.open().bind(new InetSocketAddress(port));
//设置非阻塞模式 是否阻塞
ssc.configureBlocking(false);
2. Buffer缓冲区
Position:指定下一个将要被写入或者读取的元素索引,它的值由get()/put()方法自动更新
Limit:第一个不应该读取的数据的索引,即位于 limit 后的数据不可读
Capacity:表示 Buffer 最大数据容量,缓冲区容量不能为负,并且创建后不能更改
标记 (mark) 与重置 (reset):标记是一个索引,通过 Buffer 中的 mark() 方法指定 Buffer 中一个特定的 position,之后可以通过调用 reset() 方法恢复到这个 position.
3. Selector选择器
Selector创建:Selector select=Selector.open();
Channel注册:SelectionKeykey=channel.register(selector,****);
37. 常用设计模式都有哪些
1. 单例模式:私有化构造方法,提供静态方法获取唯一实例
2. 工厂模式:创建型设计模式,通过工厂factory参数类型获取对应的实例
3. 抽象工厂:创建型设计模式,通过工厂类实现不同产品的实例获取。不同
4. 建造者模式:创建型设计模式,建造者类实现产品实例化和参数设置,指挥者提供方法,调用建造者完成参数初始化/设置,最后由建造者获取实例
5. 策略模式:行为型设计模式,在上下文类中定义策略设置和执行,通过上下文类构造方法传入具体策略实例完成上下文类实例,由调用方法执行具体策略,也可通过设置策略类完成策略切换
6. 装饰器模式:结构设计型模式,在不改变源码基础上,通过继承装饰器实现类的方式添加额外的操作。(装饰器实现类实现原组件接口并注入原接口,重写原方法时调用父类方法)
7. 适配器模式:结构型设计模式,适配器让两个类接口适应接口,主要分为对象适配器和类适配器
7.1 对象适配器:适配器持有原始对象引用,将自身接口和原始接口结合(在自身接口中调用原始接口方法)
7.2 类适配器:适配器实现了原始接口也继承了目标类,目标类持有与原始接口相同的方法,但并没有具体实现原始接口,而是在适配器中由适配器通过调用目标类的方式实现原始接口
8. 观察者模式:行为型设计模式,也叫发布订阅,定义了对象之间一对多的依赖关系,当一个对象状态改变时,所有依赖到他得对象都能收到通知并更新。
由观察着接口背多个不同类型的具体观察者实现,由抽象主题提出注册、删除、通知三个方法,由具体主题实现,在具体主题中调用这三个方法并传入一个具体观察者参数实现这三个功能
38. AOP是什么
AOP是一种编程思想,在不改变源码的基础上进行功能拓展
39. AOP采用什么代理方式
1. 静态代理:
通过代理类持有原目标的引用对象并实现原接口,调用代理类时,即完成了原接口功能,又能原功能前后实现需要添加的功能(对象适配器模式)
缺点是:一个代理类只能对应一个对象。需要代理类过多时,代码造成混乱
2. 动态代理
2.1 CGLIB代理
实现MethodInterceptor重写intercept方法,创建proxy工厂,Enhancer.create获取代理类
2.2 JDK代理
通过实现InvocationHandler接口,重写invoke方法,传入被代理类class对象,方法和需要的参数,进行添加需要拓展的功能。Proxy.newProxyInstance获取被代理类实例。只能代理实现了接口的类
40. CGLIB 和JDK 区别
JDK 动态代理只能只能代理实现了接口的类,而 CGLIB 可以代理未实现任何接口的类。
CGLIB 动态代理是通过生成一个被代理类的子类来拦截被代理类的方法调用,因此不能代理声明为 final 类型的类和方法。
JDK 效率更优于CGLIB
41. 动态代理和静态代理区别
1. 灵活性 :动态代理更加灵活,不需要必须实现接口,可以直接代理实现类,并且可以不需要针对每个目标类都创建一个代理类。另外,静态代理中,接口一旦新增加方法,目标对象和代理对象都要进行修改,这是非常麻烦的!
2. JVM 层面 :静态代理在编译时就将接口、实现类、代理类这些都变成了一个个实际的 class文件。而动态代理是在运行时动态生成类字节码,并加载到 JVM 中的。
42. 事务是ioc和还是aop,谈谈他的具体实现
事务分为两种
编程式事务:在代码中使用TransactionStatus 手动提交或者回滚
@Autowired
private TransactionTemplate transactionTemplate;
@Override
public void template()
{
User user = new User();
user.setUserName("123");
user.setCreateTime(LocalDateTime.now());
user.setUpdateTime(LocalDateTime.now());
User user1 = new User();
user1.setUserName("hhh");
user1.setCreateTime(LocalDateTime.now());
user1.setUpdateTime(LocalDateTime.now());
log.info("插入第一条数据开始========");
userMapper.insert(user);
log.info("插入第一条数据完成========");
HashMap<String, String> hashMap = new HashMap<>();
HashSet<String> hashSet = new HashSet<>();
hashSet.add("qweq");
hashMap.put("1","2");
transactionTemplate.execute(status -> {
log.info("编程式事务中:插入第一条数据开始========");
userMapper.insert(user1);
log.info("编程式事务中:插入第一条数据完成========");
log.info("编程式事务中:插入第二条数据开始========");
int insert = userMapper.insert(user);
log.info("编程式事务中:插入第二条数据完成========");
return insert;
});
}
声明式事务:使用@Transtactional,基于AOP思想
43. Springboot为什么可以实现开箱即用
Springboot内部提供了很多autoconfiguration配置类,自动配置类会去查找classpath中是否存在自己需要的类,存在就会自动配置相关配置
@AutoConfiguration //标注这是一个自动配置类
public class DyMybatisConfig
{
//sqlSessionFactoryBean
@Bean //返回值bean名称默认为方法名
public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource)
{
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource);
return sqlSessionFactoryBean;
}
@Bean
public MapperScannerConfigurer mapperScannerConfigurer(BeanFactory beanFactory)
{
MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();
//从AutoConfigurationPackages 获取包全路径
List<String> packages = AutoConfigurationPackages.get(beanFactory);
mapperScannerConfigurer.setBasePackage(packages.get(0));
//配置扫描的注解
mapperScannerConfigurer.setAnnotationClass(Mapper.class);
return mapperScannerConfigurer;
}
}
再下列文件中配置文件全路径信息
META-INF.spring
org.springframework.boot.autoconfigure.AutoConfiguration.imports
com.sc.config.DyMybatisConfig
44. Linux常用命令
ls 查询当前文件夹内所有文件命令
-a 列举目录中的全部文件,包括隐藏文件
-l 列举目录中的细节,包括权限、所有者、组群、大小、创建日期、文件是否是链接等
-f 列举的文件显示文件类型
-r 逆向,从后向前地列举目录中内容
-R 递归,该选项递归地列举当前目录下所有子目录内的内容
-s 大小,按文件大小排序
pwd 显示当前目录
grep 在文件中查找某字符
touch 命令用来创建新文件,他可以创建一个空白的文件
mkdir 创建文件夹/目录
rmdir 命令用来删除目录。
netstate
cd /usr 转到根目录下的usr目录中
cp:复制文件
-i 互动:如果文件将覆盖目标中的文件,他会提示确认
-r 递归:这个选项会复制整个目录树、子目录以及其他
-v 详细:显示文件的复制进度
mv命令用来移动文件
-i 互动:如果选择的文件会覆盖目标中的文件,他会提示确认
-f 强制:它会超越互动模式,不提示地移动文件,属于很危险的选项
-v 详细:显示文件的移动进度
rm命令用来删除文件。
-i 互动:提示确认删除
-f 强制:代替互动模式,不提示确认删除
-v 详细:显示文件的删除进度
-r 递归:将删除某个目录以及其中所有的文件和子目录
45. Dockerfile写法
46. Explan关键字有哪些字段
1. Id
2. select_type:primary、subquery、derived、union
3. table
4. type:这一列表示关联类型或者访问类型,也可以理解成mysql是如何决定查找表中的行,性能从优到差分别为:system > const > eq_ref > ref > range > index > ALL
5. possible_keys:这一列显示查询可能使用哪些索引来查找
6. key:这一列显示mysql实际采用哪个索引来优化对该表的访问
7. key_len,这一列显示了mysql在索引里使用的字节数
8. rows
47. 都有那些锁,谈谈他们的区别及用法
48. HashTable和HashMap区别
1. HashTable的方法都有关键字synchronized,所以是线程安全的,但是效率较低,要实现线程安全可以选择concurrentHashMap,它使用了分段式锁
2. 继承的类不同:HashTable是继承了Dictionary类,而HashMap是继承了AbstractMap类,但是她们都实现了Map接口
3. HashMap支持kv都可以为null,但是HashTable不支持
4. HashMap默认初始容量16,加载因子0.75,HashTable默认初始容量11,加载因子0.75
49. Spring 7大核心组件
1. 核心容器(Spring Core):创建(工厂模式+反射)管理bean容器
2. Spring上下文(Spring Context)
3. Spring面向切面编程(Spring AOP)
4. Spring web
5. Spring MVC
6. Spring DAO
7. Spring ORM
50. SpringBoot内嵌了那些容器,如何更改tomcat版本
Tomcat:默认使用 Tomcat 作为嵌入式 Web 容器。Tomcat 作为一个流行的 Web 容器,容易能够理解、配置和管理。可以通过使用spring-boot-starter-web来启用 Tomcat 容器
Jetty:嵌入式 Web 容器,它的缺省配置相对精简,从而有利快速启动。可以通过使用spring-boot-starter-jetty来启用 Jetty 容器。
Undertow:是一个由 JBoss 开发的轻量级的嵌入式 Web 服务器。它具有出色的性能和低资源占用率,是一个适合微服务实现的 Web 服务器。可以使用spring-boot-starter-undertow来启用 Undertow 容器。
Netty:是一个高性能的网络框架,需要引入spring-boot-starter-webflux和spring-boot-starter-reactor-netty来开启Netty作为Web容器
更改tamcat版本
在pom文件当中移除原tomcat版本,引入新的tomcat core依赖
移除:
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
<exclusion>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
</exclusion>
<exclusion>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-el</artifactId>
</exclusion>
<exclusion>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-websocket</artifactId>
</exclusion>
<exclusion>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-annotations-api</artifactId>
</exclusion>
</exclusions>
新增
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
<version>${tomcat.version}</version>
<exclusions>
<exclusion>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-annotations-api</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-annotations-api</artifactId>
<version>${tomcat.version}</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-el</artifactId>
<version>${tomcat.version}</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-websocket</artifactId>
<version>${tomcat.version}</version>
<exclusions>
<exclusion>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
</exclusion>
</exclusions>
</dependency>
51. Springboot如何更新容器
1. 移除原容器类型
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
2. 新增jetty依赖 其他容器类似
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
52. Spring Boot优点
1. 独立运行:可以打成可执行的jar包
2. 简化配置:
3. 自动配置:
4. 无代码生成和xml配置
5. 应用监控:健康检测
53. Spring Boot核心注解
1. @SpringBootConfiguration:实现配置功能
2. @EnableAutoConfiguration:打开自动配置
3. @ComponentScan:Spring组件扫描
54. 同类方法调用如何实现事务
1. 使用编程式事务解决同类事务相互调用问题(代理)
<--1. 引入依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<--2. 启动类开启注解-->
@EnableAspectJAutoProxy(exposeProxy = true)
<--3. 创建代理对象-->
Aservice a=(Aservice)AopContext.currentProxy();
2. 注入TransactionTemplate
@Autowired
private TransactionTemplate transactionTemplate;
transactionTemplate.execute(status -> {
});
3. 声明书事务缺点
1. 使用Transactional必须为public方法
2. 必须往外抛出异常
3. 同类中事务方法相互调用会导致事务时效
55. 怎么保证redis和数据库的一致性
1. 先更新数据库,在更新缓存:高并发情况,数据库更新成功,缓存没成功,数据不一致。
2. 先删除缓存,在更新数据库:缓存删除成功,但数据库还未更新,数据不一致
3. 延时双删。先删缓存,在更新数据库,在删缓存。要接收短时间数据不一致
4. Mq消息队列
5. Cannal实时监听binarylog日志。都要接受短时数据不一致
6. 读写锁ReentrantReadWriteLock.writelock.lock。保证数据抢一致性,在写操作时,其他线程不能操作
56. 数据库优化
1. 数据库服务器内核优化
2. My.cnf配置文件优化,搭配压力测试调优
3. Sql语句优化(常用---面试主问)
57. Sql优化
1. 善用limit
2. 模糊匹配尽量精确最左或最右,like ‘%***’
3. Union尽量加all,union会去重+排序,降低了效率。而union没有这些,如果需要利用代码实现。服务器资源 < 数据库资源
4. 善用exists和in,主查询数据少用exists,循环主查询结果在子结果寻找。主查询数据大用in,在子结果中寻找主查询结果匹配到的数据。核心:用小表驱动大表
5. 善用truncate和delete:truncate,效率高,性能好,可立即释放表空间和索引占用的资源,但不会被日志所记录,不能回滚。Delete一行行清除表数据,性能低,不会立即释放占用资源,可以背日志记录和回滚。
6. 尽量批量操作,可减少大量io资源消耗。最大限制65535
7. 优先过滤、过滤操作大于其他,有效减少数据量
8. 在有索引列上使用函数,会使函数失效。所以函数尽量在等号右侧
9. 数据类型最小可用。小数据类型占用数据库资源更低,处理效率更高
10. Char和varchar,char定长,效率更高。Varchar变长更节省空间
11. Varchar长度选择最小可用,可以帮助sql排序优化性能
12. 适当索引策略,频繁查询、统计、分组、多表关联字段、排序字段适合索引
13. Delete和update语句中where条件必须索引,否则会导致锁表
14. Force index(括号内跟索引名)强制使用某个索引
58. 红黑树和二叉树区别
二叉树:新插入的节点如果比父节点数据小就在父节点右侧插入。会一直往下插入
红黑树:也叫平衡二叉树,会将两个数据的中间节点作为索引放到上级父节点下。
b-树:增强型红黑树,红黑树一个节点只包含一个索引,但是b-树每个节点可以包含很多索引和数据。节点数据从左往右依次递增
b+树:非叶子结点(最底层节点)不存储数据,只存储索引(每层节点都会有冗余索引,在底层同样会有,叶子结点包含所有索引字段)和指向下子节点的指针。和b-树区别:节点不包含数据,只存索引,效率更好,性能更高。
59. Map扩容机制
1. 先计算新数据hash
2. 判断是否需要初始化或扩容
3. 根据新节点计算得到的hash判断当前位置是否存在元素。不存在则直接插入
4. 计算得到的节点存在新元素
4.1 判断key是否一致,一致则覆盖旧值
4.2 不一致则看是否为树结构,是树则直接加入树结构中
4.3 不为树,则遍历链表,新加入元素后的链表长度大于8则根据map长度判断是否大于64,大于64则转换为红黑树,否则继续扩容,不转树
60. Synchronized和lock
1. 功能:都是保证并发场景下的线程安全问题
2. 特性:Synchronized是java提供的同步关键字
Lock是juc包提供的接口,有很多实现方法,包括reentrantlock重入锁
3. 灵活性:Lock比Synchronized更灵活
3.1 Synchronized:力度控制方式两种
方法层面:
public Synchronized void sync()
{
//*****
}
代码层面:
object lock=new object
public void sync()
{
Synchronized(lock)
{
//****
}
}
lock:通过lock和unlock加锁和释放锁,可以通过trylock返回的true和false知道锁是否被占用
4. 性能:Lock比Synchronized性能相差不大,实现方式不一样
4.1 Lock:使用了自旋锁实现了锁优化
4.2 Synchronized:引入了偏向锁,轻量级锁、重量级锁以及锁升级实现锁优化
5. 机制:lock提供了公平锁和非公平锁机制,而Synchronized只提供了非公平锁
公平锁:锁队列有其他线程在排队,当前线程无法参与竞争锁
非公平锁:无论有没有排队都要参与一次锁竞争
无论是公平锁盒非公平锁都只体现在加锁阶段,而不会在线程唤醒阶段,线程唤醒时值接唤醒排队最前面的
6. 层次:lock是api层面,Synchronized是jvm层面
61. Synchronized的锁升级
1. 锁升级:在没有并发压力时,锁选择为最小资源消耗,当并发压力逐渐增大时,会重新选择锁的实现方式
2. 实现方式:
1. 偏向锁:在所对象的对象头中存储线程id,如果下次这个线程继续来获取锁可以直接获取到,也就是说他支持重入锁
2. 轻量级锁:在两个及两个以上线程交替获取锁资源时(交替不是并发获取),此时升级为轻量级锁,轻量级锁采用cas自旋获取,循环获取锁资源,获取到就去执行,资源消耗属于jvm层面,不会太消耗系统资源
3. 重量级锁:在两个及两个以上线程并发获取锁资源时,升级成重量级锁,此时由操作系实现,由用户态切换成内核态,可以避免无用自旋导致的cpu消耗
62. 位运算符
1. &按位与
二进制:相同位置值相同则为1 否则为0
举例:0000 1001 和0101 0101 结果为 0000 0001
2. &&
3. |按位或
二进制:相同位置值有1则为1
举例:0000 1001 和0101 0101 结果为 0101 1101
4. ~按位取反
二进制:相同位置值取反
举例:0000 1001 结果为 1111 0110
二:面试题自我总结
1. 我们的服务实例是如何注册到Nacos服务的
Nacos本身就是一个web服务,我们将一些数据传递到这个web服务,然后在我们的业务服务启动时会向nacos发起一个http请求,然后nacos服务端从请求中读取数据并进行存储
自我理解:
2. 服务提供者是如何向Nacos注册中心(Registry)续约的?
5秒心跳(5秒一次,15不健康 30 不可用)
3. 为什么要将服务注册到nacos?
为了更好的查找这些服务
自己百度理解:服务消费者发现服务
3. nacos服务来讲它是如何判定服务实例的状态
(检测心跳包,15,30)
4. 服务启动时如何找到服务启动注册配置类?
NacosNamingService
5. 服务消费方是如何调用服务提供方的服务的
答:(RestTemplate)RestTemplate 是由 Spring 提供的一个 HTTP 请求工具
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
//普通写法
RestTemplate restTemplate = new RestTemplate();
//代理:
RestTemplate restTemplate=new RestTemplate(new HttpComponentsClientHttpRequestFactory());
//okhttp
RestTemplate restTemplate = new RestTemplate(new OkHttp3ClientHttpRequestFactory());
6. Nacos中的负载均衡底层是如何实现的?
7. Ribbon 是什么?
Netflix公司提供的负载均衡客户端,一般应用于服务的消费方法
8. Ribbon 可以解决什么问题?
基于负载均衡策略进行服务调用, 所有策略都会实现IRule接口,核心方法是choose,用于选择一个服务实例
9. Ribbon内置的负载策略都有哪些?
8种,可以通过查看IRule接口的实现类进行分析
1. RandomRule:随机选择一个服务实例:生成一个不大于服务实例数的随机数作为服务实例的下标来获取服务
2. RoundRobinRule:轮训负载:循环遍历服务清单,再者之前先通过incrementAndGetModulo获取一个下标,这个下标是通过当前服务索引值+1和服务总数取模得到,然后根据下标找服务,连续10次没找到就报警告
3. RetryRule:重试性:实现类还是RoundRobinRule线性负载,只不过在获取到失效或null的服务实例后,会在失效时间deadline内不断重试,重新获取可用服务,超时后返回一个null
4. WeightedResponseTimeRule:权重:通过DynamicServerWeightTask定时任务没30s计算各服务权重值(根据服务平均响应时长,越短权重值越高。)权重越高执行任务的概率越大
5. ClientConfigEnabledRoundRobinRule等同于RoundRobinRule
6. BestAvailableRule继承至ClientConfigEnabledRoundRobinRule,增加一个过滤器,根据loadBalancerStats保存的服务状态信息过滤掉失效服务器,然后找出并发最小的服务实例使用,如果状态为空就用父类方法
9. PredicateBasedRule是ClientConfigEnabledRoundRobinRule的一个子类, 随机过滤一部分服务实例,然后采用线性负载策略
10. ZoneAvoidanceRule是PredicateBasedRule的一个实现类:复合判断server所在区域的性能和server的可用性选择service
10. @LoadBalanced的作用是什么?
描述RestTemplate对象,用于告诉Spring框架,在使用RestTempalte进行服务调用时,这个调用过程会被一个拦截器进行拦截,然后在拦截器内部,启动负载均衡策略。
11. 我们可以自己定义负载均衡策略吗?
可以,基于ReactorServiceInstanceLoadBalancer接口进行策略定义
12. @Bean注解的作用?
一般用于配置类内部,描述相关方法,用于告诉Spring此方法的返回值要交给Spring管理,bean的名字默认为方法名,假如需要指定名字可以@Bean(“bean的名字”),最多的应用场景是整合第三方的资源-对象
13. @Autowired注解的作用?
此注解用于描述属性,构造方法,set方法等,用于告诉Spring框架,按找一定的规则为属性进行DI操作,默认按属性,方法参数类型查找对应的对象,假如只找到一个,则直接注入,类型多个时还会按照属性名或方法参数名进行值的注入,假如名字也不同,就出报错.
14. 什么是http协议?
超文本传输协议:用于服务器传输到本地浏览器的传送协议,使浏览器更加高效,网络传输资源减少,一次请求,一次响应,只能由客户端发起请求,服务器响应
15. 正向代理和反向代理
正向代理:类似于跳板机,客户端设置的正向代理代理访问外部资源,客户端没办法直接访问到外部资源,但有代理服务器可以访问到,通过访问代理服务器向外部资源访问。----对外隐藏了客户信息
反向代理:服务器设置的反向代理,用来接受客户端发来的请求,通过反向代理服务器将请求转发给内部服务器,主要是负责负载均衡---通过反向代理优化网站负载
16. JDBC步骤
1. 加载驱动---加载jar包: class.forName(com.mysql.Jdbc.Driver);
2. 获取链接DriverManger.Connection(jdbc:mysql:ip:3306/jdbc,root,root)
3. 获取传输器:connection.createPrepareStatement
4. 执行sql:ExecuteQuery
5. 返回数据
6. 释放资源:链接的数据库和传输器,结果
17. sql注入?
用户输入恶意字符# 或者1=1这种永远满足条件的时候,同样可以从数据库中获取信息
解决方案:使用JDBC传输器时使用preparestatement
18. #和$的区别
#{} ${}: 是mybatis实现动态sql的基础,都是为了动态传参
1.编译区别:#{} 预编译:在在执行sql之前预编译, 提升了代码性能,缓存PreparedStatement对象,对于同一个sql下次执行时,无需再次编译,可直接使用
2. #{} 会自动拼接单引号 $ 不拼接单引号
3. #{} 占位符:能防止sql 注入 ${} 拼接符:不能防止sql注入 )
19. nginx 是什么
是一个高性能的http和反向代理服务器,内存CPU占用资源低,运行稳定,为了解决并发---负载均衡
20. 负载均衡的方法
轮训:轮着来
权重:设置较好的服务器做更多的活
iphash:根据算法决定
21. 什么是mybatis
是一个持久层框架, 封装了jdbc操作数据库的过程, 自动完成了ORM映射, (类属性和表字段的一一映射)
1. 加载核心配置文件spring-config.xml:数据库的驱动、url、用户名密码)
2. 创建UserMapper.xml用来写sql的文件,namespace文件唯一标识,id对应sql语句
3. 代码:Resources.getResourceAsStream("mybatis-config.xml")加载核心配置文件
4. New sqlsessionfactorybuilder().build(in); 创建会话工厂
5. Factory.oppensession(); 创建会话
6. Session.selectlist(“文件唯一标识”.”id”) )
22. ArraysList和Linkedlist的区别
ArraysList 基于索引的数组结构:默认初始容量为10, 不够时会以1.5倍的容量增长查询效率更高
LinkedList 双向链表:在添加和删除时性能更优于数组,因为他不需要连续的空间
23. 接口和抽象类的区别
接口:可以多继承多实现,在jdk1.8以前没有方法实体,实现要重写所有抽象方法,默认的变量为常量不可更改
抽象类:可以有普通方法、抽象方法,只能被单继承,继承后要实现所有抽象方法,抽象类可以没有抽象方法
24. 线程的五大状态
新建(new Thread),可运行(Runnable),运行(Running),阻塞(),死亡(Dead)
阻塞分为:
等待:调用.wait方法,线程释放锁,在等待队列waitting queue等待被调用;
同步:调用同步锁时,同步锁被其他线程调用,线程在lock pool等待
其他阻塞:执行了.sleep等方法,状态超时后,重新进入可运行状态,不释放锁,正常醒了继续执行程序
获取线程状态方式:thread.gestate()
25. 创建线程的四大方式
1. 继承thread重写run方法,然后new一个线程
public class CreateThreadByThread extends Thread
{
@Override
public void run()
{
System.out.println(getName());
System.out.println("CreateThread By Thread is run!!!"+Thread.currentThread().getName());
}
}
//继承thread----本质也是实现runnable 重写run方法
CreateThreadByThread createThreadByThread = new CreateThreadByThread();
createThreadByThread.start();
try
{
Thread.sleep(2000);
}catch (InterruptedException e)
{
throw new RuntimeException(e);
}
2. 实现runable接口重写run方法:实现runable接口,new一个thread传入一个参数为实现runable接口的对象
public class CreateThreadByRunnable implements Runnable
{
@Override
public void run()
{
System.out.println("CreateThread By Runnable is run!!!"+Thread.currentThread().getName());
}
}
//实现runnable
CreateThreadByRunnable createThreadByRunnable = new CreateThreadByRunnable();
Thread thread1 = new Thread(createThreadByRunnable);
thread1.start();
try
{
Thread.sleep(2000);
}catch (InterruptedException e)
{
throw new RuntimeException(e);
}
3. 实现callable重写call方法:需要创建FutureTask<String>对象来获取call方法返回值,并且在创建thread对象是传入FutureTask<String>对象
public class CreateThreadByCallable implements Callable<List<User>>
{
@Override
public List<User> call()
{
System.out.println("CreateThread By Callable is run!!!"+Thread.currentThread().getName());
return Collections.singletonList(new User(1, "2"));
}
}
FutureTask<List<User>> futureTask = new FutureTask<>(new CreateThreadByCallable());
Thread thread2 = new Thread(futureTask);
thread2.start();
List<User> users = futureTask.get();
System.out.println(users);
4. 线程池创建线程:Executors.newFixedThreadPool创建线程池,执行线程,传入实现runable接口的对象,只适用于实现了runable接口创建的线程
public class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + "输出了:HelloWorld" + i);
}
try {
System.out.println(Thread.currentThread().getName() + "本任务与线程绑定了,线程进入休眠了~~~");
Thread.sleep(10000);
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* 线程池创建线程
* corePoolSize :要保留在池中的线程数
* maximumPoolSize :允许的最大线程数
* keepAliveTime:当线程数大于核心时,这是多余空闲线程在终止前等待新任务的最长时间
* unit:{@code keepAliveTime}参数的时间单位
* 1. 最大线程数maximumPoolSize
* 2. 核心线程数corePoolSize
* 3. 活跃时间keepAliveTime
* 4. 阻塞队列workQueue
* 5. 拒绝策略RejectedExecutionHandler
*/
ExecutorService pool = new ThreadPoolExecutor(3, 5 ,6, TimeUnit.SECONDS, new ArrayBlockingQueue<>(5) , Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy() );
//要执行的代码
Runnable target = new MyRunnable();
pool.execute(target);
26. Post和Get 的区别
Post 隐藏提交数据,不会拼接到url地址栏,保密性更高,安全性更强,传输占用资源更少,传输效率更高
Get自动拼接数据到url地址栏,安全性差,传输占用资源多,效率低,适合查询
27. session的生命周期
session:用来存储用户数据的,session一定时间内保存到服务器内存中,当退出或者超时后会被自动清除缓存,下次登录时需要重新输入信息,一定程度上会占用你服务端的性能,但更安全
Cookie:保存到客户的浏览器上,不是很安全,但是访问较多时,性能较session高
28. Spring AOP五大通知类型
前置通知: before:在连接点前面执行,前置通知不会影响连接点的执行,除非此处抛出异常。
正常返回通知:After Returning:在连接点正常执行完成后执行,如果连接点抛出异常,则不会执行。
异常返回通知:After Throwing:在连接点抛出异常后执行。
返回通知:After(finally):在连接点执行完成后执行,不管是正常执行完成,还是抛出异常,都会执行返回通知中的内容。
环绕通知:Around:负责决定是继续处理join point(joinPoint.proceed)还是中断执行,还在方法调用前后自定义一些操作
执行顺序:前置通知Before→环绕通知Around→返回通知(After )→正常返回通知(After Return)/异常返回通知(After Throw)
29. Mybatis基于接口开发时四大基本规则
1. 接口的路径和名称必须和.xml文件中的namespace文件名称一致
2. 接口中的方法名和.xml文件名中的id一致
3. 接口中方法传递参数类型和传输器指定类型一致
4. 接口中方法返回值类型和传输器指定类型一致
30. SpringMVC核心组件
DispatcherServlet:前端控制器—>用来接受转发请求,降低了组件间的耦合性
HandlerMapping:处理器映射器-->根据url找到对应的Handler,找到对应的controller,返回一个结果告诉DispatcherServlet去哪里调用那个方法
HandlerAdapter:处理器适配器--> 去对应controller执行方法,返回ModelAndView
ModelAndView:封装模型信息和需要返回的数据
ViewReservlet:视图解析器,将封装后的数据填入到对应的模板中
View: 展示给用户
31. Springboot的特性
内置servlet独立容器,开箱即用,简化了编码、配置、部署、监控
开箱即用:内部配置了许多config配置类,在mete-info中引入这些配置,会根据全路径信息自动查找这些配置类,当找到后就会自动装配置
32. 堆和栈的概念及区别
堆:存储的实体对象,每一个new出来的对象都在这里面,堆不会随时被释放,但可能不定时被GC机制回收,
栈:存储的是局部变量地址值,通过栈中的地址值去堆内存找到对应的实体,生命周期较短,结束就会被释放
33. Spring Cloud核心组件
服务注册与发现组件——Eureka
负载均衡——Ribbon
远程调用———Feign
断路器组件——Hystrix
配置中心组件——Config
消息队列组件——Stream
网关组件——Zuul
Zuul:
使用的是阻塞式的 API,不支持长连接,比如 websockets。
底层是servlet,Zuul处理的是http请求
没有提供异步支持,流控等均由hystrix支持。
依赖包spring-cloud-starter-netflix-zuul。
Gateway:
Spring Boot和Spring Webflux提供的Netty底层环境,不能和传统的Servlet容器一起使用,也不能打包成一个WAR包。
依赖spring-boot-starter-webflux和/ spring-cloud-starter-gateway
提供了异步支持,提供了抽象负载均衡,提供了抽象流控,并默认实现了RedisRateLimiter。
34. String为什么是不可变字符串
底层是char数组被final修饰
35. 什么是Redis
跨平台的高性能kv存储结构的非关系型分布式缓存数据库
36. Redis特点
1. 支持数据持久化,将内存中的数据保存到磁盘中,下次启动时可以从磁盘直接获取
2. 存储数据类型丰富:kv、list、set、hash、string(最大存储容量512M)
3. 支持数据的备份,即 master-slave 模式的数据备份。
4. 性能高,支持事务,操作都是原子性(全成功或全失败)
5. 丰富的特性:可根据key值设置缓存、消息,过期时间
6. 单进程单线程:利用队列技术将并发转为串行,且消除了传统数据库串行开销
37. Redis持久化机制
1. RDB
1.1 概念
持久化时将数据持续写入到临时文件dump.rdb文件中,
持久化结束,该文件替换上次持久化文件
1.2 优点
1.2.1 只有一个文件 dump.rdb,方便持久化
1.2.2 容灾性好,一个文件可以保存到安全的磁盘。
1.2.3 性能最大化,主进程继续执行命令,子线程完成写操作,
1.2.4 大数据恢复时比AOF速率更快
1.3 缺点
1.3.1 安全性差:持久化期间出现宕机,会丢失持久化这段时间得数据
2. AOF
2.1 概念
记录命令行保存为AOF文件
2.2 优点
2.2.1 数据安全,每执行一次命令都将保存到AOF文件
2.2.2 通过 append 模式写文件,即使宕机也可以使用AOF文件恢复工具恢复数 据
2.2.3 AOF 机制的 rewrite 模式,文件过大时,会对命令合并重写,也可以删除某些误操作命令
2.3 缺点
2.3.1 AOF 文件比 RDB 文件大,且恢复速度慢。
2.3.2 数据集大的时候,比 RDB启动效率低。
38. Redis回收策略
1. volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
2. volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
3. allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘
4. allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
5. no-enviction(驱逐):禁止驱逐数据
39. Redis缓存方式
1. 配置Redis连接信息
2. 配置ObjectMapper ,注入SpringDataRedis和RedisCacheManager
3. @Cacheable(根据方法的请求参数对其结果进行缓存。根据Key进行判断,如已在缓存中,则不执行方法,直接返回结果)
40. Redis数据结构
1. String(字符串):String 类型是 Redis 最基本的数据类型,String 类型的值最大能存储 512MB
2. Hash(哈希):
Redis hash 是一个键值(key=>value)对集合
Redis hash 是一个 string 类型的 field 和 value 的映射表,hash 特别适合用于存储对象
3. List(列表):Redis 列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)
4. Set(集合):Redis的Set是string类型的无序集合
5. zset(sorted set:有序集合):Redis zset 和 set 一样也是String类型元素的集合,且不允许重复的成员
41. 缓存雪崩、击穿、穿透
1. 雪崩(redis宕机或者大量热点信息过期)
宕机或者短时间内大量热点信息过期,导致缓存中无数据,数据库有
解决方案:多级缓存->本地-redis-数据库、高可用集群、随机过期时间
2. 击穿(某个热点key过期)
大量并发访问某个key过期的数据或者缓存区和数据库不存在的数据
解决方案:接口层校验传递的参数、缓存空对象,设置过期时间
3. 穿透(恶意请求)
缓存中和数据库都没有且同时并发大
解决方案:设置热点信息永不过期、接口限流熔断降级
42. 什么是事务
由一组sql组成,要么全成功,要么全失败回到操作之前状态,事务是数据库的最小操作单元
43. 事务关键字
开始:start transaction;- 或begin;
提交:commit
回滚:rollback;
44. 事务特性ACID
A - 原子性 Atomic:事务中的sql要么都做,执行要么都不执行
C - 一致性 Consistency:数据库总体前后一致不改变
I - 隔离性 Isolation:一个事务执行时不会被其他事务干扰
D - 持久性 Durancy:一个事务提交过后,对数据的改变是永久的,之后的事务操作或故障不会对该事务产生影响
45. 事务并发问题
脏读:读其他事务未提交的数据
幻读:读到了已删除或者读不到新插入的数据
不可重复读:一个事务中多次读取到的数据前后不一致
46. 事务隔离级别
1. READ-UNCOMMITTED:读未提交---三种问题都有
2. READ-COMMITTED:不可重复读(读已提交)--解决了脏读问题
3. REPEATABLE-READ:可重复读,执行时拍摄快照,该事务中任何时候查询结果一致 ,不能解决幻读,Mysql默认级别
4. SERIALIZABLE:串行--隔离级别最好,依次执行事务,可解决所有问题---效率最 低
47. Mysql索引是什么
1. 一种排好序的快速查找的数据结构,帮助数据库高效的进行数据的检索,额外的存储空间
1.1 优点
1. 提高了检索效率,降低了磁盘IO成本
2. 优先对数据进行排序,查询时降低了排序成本,降低了CPU消耗
1.2 缺点
1. 索引本身也是一张表,存储了主键和索引字段及指向实体表的记录
2. 索引中有的表中也有,数据重复,空间浪费
3. 提高了查询,降低了其他操作效率,数据内容改变应及时更新索引,如果业务量大时,更新索引表困难
48. Mysql索引分类
1. 单值索引:一个索引只包括一个列,一个表可以有多个列
2. 唯一索引:索引列的值必须唯一,但允许有空值;主键会自动创建唯一索引
3. 复合索引:一个索引同时包括多列
49. MySQL中的删除:drop,delete,truncate的区别和联系
1. drop是DDL(数据定义语言),用于整张表的删除,删除的时候不仅删除了表中的数据,还删除了表结构
2. delete是DML(数据操作语言),用于对表中数据的删除,不会删除表结构
3. truncate是DDL(数据定义语言),使用该命令可以删除表中所有数据,但不会删除表结构。truncate的删除原理是重新创建一个表(不包含数据),然后将原来的表删除。
4. 区别
4.1 都可以删除整张表中的数据
4.2 删除的范围:drop(删除表中所有数据及表结构)>truncate(删除表中所有数据)>=delete(删除表中所有数据或部分数据)
4.3 查询条件:delete可以使用查询条件进行表中数据删除,drop和truncate不可以
4.4 命令类型:delete属于DML,drop和truncate属于DDL
4.4 数据能否恢复:delete删除的数据可以恢复,但是drop和truncate删除的数据不能恢复
4.5 执行效率:drop>truncate>delete
50. Mysql约束
1. 非空 not null
- 添加非空约束的字段,值不能为null
2. 唯一 unique
- 添加唯一约束的字段,值不能重复,可以为null
3. 主键约束 primary key
- 添加了主键约束的字段,值不能为null也不能重复
4. 默认约束 default
- 给字段设置默认值,当字段不赋值的时候,默认值生效
51. Docker是什么
Docker 是一个开源的应用容器引擎,基于 Go 语言并遵从 Apache2.0 协议开源
Docker 包括三个基本概念:
镜像(Image):Docker 镜像(Image),就相当于是一个 root 文件系统。比如官方镜像 ubuntu:16.04 就包含了完整的一套 Ubuntu16.04 最小系统的 root 文件系统。
容器(Container):镜像(Image)和容器(Container)的关系,就像是面向对象程序设计中的类和实例一样,镜像是静态的定义,容器是镜像运行时的实体。容器可以被创建、启动、停止、删除、暂停等。
仓库(Repository):仓库可看成一个代码控制中心,用来保存镜像。
52. Docker基本命令
Docker ps -a :查询所有容器(包括运行中和未运行中)
Docker ps|grep 容器名称/或者id:根据名称或id查询容器
Docker start 容器id:启动容器
Docker restart 容器id:重启容器
Docker rm 容器id:删除容器
Docker rmi 镜像id:删除镜像
Docker exec -it 容器id sh 进入容器
docker version:显示 Docker 的版本信息
docker images:列出本地主机上已有的镜像
docker pull:拉取远程仓库的镜像
docker run:创建一个新的容器并运行一个命令
docker logs:查看容器的日志
53. jvm虚拟机组成
类装载子系统:用来将.class文件加载到内存数据区
字节码执行器:用来执行内存数据区代码
内存数据区: 堆和方法区都是线程共有的,栈、本地方法栈、程序计数器都是线程私有的
堆:new出来的对象
新生代占总的三分之一、老年代占总的三分之二
新生代分为Eden占8/10和survivor分为s0和s1各占1/10
垃圾回收机制:
由字节码执行器发起垃圾回收线程执行minor gc
可达性分析法:当eden区域满时,从栈区域根节点向下寻找被引用的对象并标记为非垃圾对象,然后将所有非垃圾对象复制到s0区域,同时分带年龄+1,eden剩下的就是需要被回收的垃圾对象。从s0到s1操作一样,每次回收都会被用那么这个对象就会在s0和s1间反复横跳,每次复制分代年龄+1,直到分代年龄到15以后就会复制加入到老龄代
full gc:由字节码执行器发起的full gc
新生代一直有被引用,且老年代空间区域已满。
栈:
每个线程都会分配一个栈空间,先进后出,用来存储局部变量和对象内存地址
栈空间会分配很多栈帧区域,每个栈帧区域存储对应方法的局部变量
先进后出:A调B,栈中先存A栈帧区,在存B栈帧区,B结束B栈帧区销毁(出栈)
1. 局部变量表:存储局部变量
2. 操作数栈:存储需要操作的变量
3. 动态链接:符号引用替换为直接引用,所谓的符号引用是指每个方法,直接引用是指方法在内存中的实际位置链接,在一个方法调用另一个方法时,动态链接存的就是B方法的内存位置链接
4. 方法出口:A调用B时,方法出口记录的就是A执行完B方法后需要执行的A的下一行需要执行的代码信息
方法区:常量、静态变量、加载的类信息
本地方法栈:被native修饰的方法被使用时,需要分配的栈区域从该地方获取
程序计数器:记录了下一行代码需要执行的位置
为了保证并发情况下,cpu被抢占去执行其他任务结束后能回到当前任务继续执行,而不需要从头开始
由字节码执行器指挥执行程序计数器,去修改需要操作的代码位置
54. jvm调优
1. 目的:减少STW的发生
1. 线程分为用户发起的和系统线程,full gc由系统发起,当full gc时,用户操作会发生卡顿,此时就是发生STW:停掉整个系统的用户线程。调优本质就是减少系统卡顿
2. STW设计原理:如果没有STW,当用户线程操作时,full gc也在执行,用户线程执行完毕后,full gc还没执行完成,但由于之前被引用的对象是被用户线程操作的,已经被标记为非垃圾对象,此时用户线程结束,full gc未结束,不能正确识别对象是否为垃圾。
2. GC机制
1. minor gc
1. 回收新生代垃圾对象:当eden区对象占用内存达到阈值或s0/s1区满时
2. full gc
1. 回收老年代垃圾对象:当老年代内存满时触发,清空所有堆内存
3. parallel回收
1. 每次触发gc时都会情况全部内存,比如全部eden区或s0/s1区
2. 缺点:当堆内存太大时,清空堆内存需要的stw时间较长,对系统对产生严重的卡顿现象
4. G1
1. 每次触发之清空规定大小的内存空间
2. 避免了parallel回收机制的缺点,有效降低系统卡顿现象
3. 工具
JDK 自带工具(jdk/bin 目录下):jconsole,jvisualvm,jmap -histo,jstack
阿里巴巴:arthas
4. Arthas使用
https://github.com/alibaba/arthas/blob/master/README_CN.md
55. 为什么不建议使用Executors.newFixedThreadPool(10)创建线程池
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
1. 从上述源码可以看到,他底层也是使用的new ThreadPoolExecutor,但是传入了一个无界阻塞队列LinkedBlockingQueue,她默认大小是int的最大值,当任务过多一直往队列中添加时,可能会导致oom
56. 线程池的五大状态
1. 运行running:新建/调用了execute方法,处于运行状态可以执行新的任务
2. 关闭shutdown:线程池调用shutdown方法后,不在接受新的任务,会将队列任务执行完毕
线程池内原本任务会继续执行
3. 停止stop:线程池调用shutdownNow方法后,不在接受新的任务,队列任务也不在执行,线程池内原本任务会继续执行
4. 整理tidying:中间状态不做任务处理,可以自定义线程池继承ThreadPoolExecutor重写改方法,添加上自己想要的操作
5. 终止terminated:线程池所有的任务全部完成
57. ThreadLocal的应用场景及原理
1. ThreadLocal:是java提供的本地线程存储机制,每一个线程都有一个threadlocal,必要时可以将数据存储在内部,可以在任意时刻访问,内部是key:value的键值对,底层是一个threadlocalMap,其中的set和get方法就是put和remove,key就是当前线程对象,value就是你存储的值,在set的时候会去获取
源码:
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
map.set(this, value);
} else {
createMap(t, value);
}
}
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
2. 缺点
容易造成内存/安全泄漏,需要在每次线程使用完毕后调用threadlocal.remove方法清楚threadlocalMap。因为线程是不会被销毁的,而且thread是通过强引用指向threadlocalmap的,所以threadlocamap中的值也不会被gc回收
3. 应用场景:需要共享变量各自使用但互不影响
1. 跨层参数传递,A->B->C。由A存储C使用,可以直接存到threadlocal中,而不需要作为参数一层层传递
2. 隔离线程:存储一些不安全的工具对象(SimpleDateFormat)可以在多线程情况保证并发安全
3. spring中的事务管理器(@Transactional)就是使用了threadlocal,所以在多线程或异步时尽量不要使用Transactional,可以使用编程式事务,手动开启和关闭
4. springmvc中的HttpServletRequest,HttpServletResponse,HttpSession都是放在ThreadLocal,因为servlet是单例的,而springmvc允许在controller类中通过@Autowired配置request、response以及requestcontext等实例对象。底层就是搭配Threadlocal才实现线程安全。
58. tomcat为什么要使用自定义加载器
1. jdk自带的加载器中双亲委派机制会导致每个类只加载一次,而且只会加载顶级父类,这样可以保证系统安全性
2. tomcat可以有很多应用,每一个应用可能出现同一个类,所以需要每个应用都加载该对象,自定义加载器可以实现类隔离,不出现冲突
59. 微服务的几大解决方案
特点 | Dubbo | Spring Cloud Netflix | Spring cloud Ailbaba |
开发语言 | java | java | java |
服务治理 | 提供完整的服务治理功能 | 提供部分服务治理功能 | 提供完整的服务治理功能 |
服务注册与发现 | ZooKeeper/Nacos | Eureka/Consul | Nacos |
负载均衡 | 自带负载均衡策略 | Ribbon | Ribbon\Dubbo负载均衡 |
服务调用 | RPC方式 | RestTemplate/Feign | Feign/RestTemplate/Dubbo |
熔断器 | Sentinel | Hystrix | Sentinel/Resilience4j |
配置中心 | Apollo | Spring Cloud Config | Nacos Config |
API网关 | Higress/APISIX | Zuul/Gateway | Spring CloudGateway |
分布式事务 | Seata | 不支持分布式事务 | Seata |
限流和降级 | Sentinel | Hystrix | Sentinel |
分布式追踪和监控 | Skywalking | Spring Cloud Sleuth+Zipkin | SkyWalking或SentinelDashboard |
微服务网格 | Dubbo Mesh | 不支持微服务网格 | Service Mesh(Nacos+Dubbo+Mesh) |
社区活跃度 | 相对较高 | 目前较低 | 相对较高 |
孵化和成熟度 | 孵化较早,成熟度较高 | 成熟度较高 | 孵化较新,但迅速发展 |
60. 微服务治理组件
1. 注册中心:eureka,nacos,consul
2. 配置中心:nacos config, springcloud config
3. 远程调用:rpc(dubbo),restful(restemplate,feign)
4. API网关:gateway,zuul
5. 分布式事务:seate
6. 熔断器/限流降级:hystrix,sentinel
7. 分布式追踪监控:Spring Cloud Sleuth + Zipkin,SkyWalking、Sentinel Dashboard
61. 什么是分布式和微服务区别
1. 分布式是指讲一个系统各个组件分别部署在不同的服务器上,各个组件之间通过网络通信进行协调调用,分布式主要强调一个部署方式,目的是为了解决单点故障问题这也是分布式的特性,其次是为了解决性能问题
2. 微服务是指一个系统架构,通过拆分业务区分不同模块并部署在服务器上,并不一定是分布式,因为他可以部署在同一服务器。使用微服务需要很多治理组件:注册中心、远程调用、配置中心、网关等
62. 什么是CAP
1. consistency一致性:所有节点在同一个时间看到的数据是一致的。节点之间通过加锁的方式实现,当有节点数据更新时,会对其他节点进行加锁,直到节点数据全部同步完成,锁才会被释放,但在同步时会对其他节点数据读取产生影响,增加了响应时长,所以和可用性互斥
2. availability可用性:是指保证服务一直可用且响应时长正常。可以理解为追求性能
3. partition-tolerance分区容灾性:一个节点挂掉不影响其他节点的正常运行和对外提供服务,这也是分布式系统的最主要特性,不可缺失
63. 什么是BASE
1. 基于CAP理论演化而来,即使不能保证强一致性也要保证最终一致性
2. Basically Abailable 基本可用:一种妥协,在系统出现故障/过载时,以牺牲部分功能的可用性为代价保证核心功能正常运行
1. 流量消峰:错开系统各个时间段对对各个区域的请求
2. 延迟响应:异步处理/队列,先返回等待响应,后台等待处理,结束后在更新状态
3. 体验降级:非实时数据(历史缓存),图片采用小图片/模糊处理
4. 熔断/限流:直接随机拒绝/满队列后新请求拒绝/超时等待拒绝等
5. 故障隔离
3. Soft State 软状态:CAP中的一致性就是硬状态,要求同一时间所有数据保持一致。而软状态是中间状态且认为该状态不影响系统正常运行,且短时间允许各个节点数据不一致,但是最终结果要保持一致
4. Eventually Consistent 最终一致性:通过软状态后各个节点数据保证一致
64. 说说分布式事务
1. 事务的四大特性
2. 分布式事务是指多个事务组件合并完成一个事务:通过分布式事务协调各个组件的事务一起提交或者回滚
3. 常见分布式事务解决方案
1. 2PC: 由事务协调者向各个组件发起通知插入事务,由各个组件返回给协调者事务处理情况,由协调者决定所有事务最终走向,回滚还是提交
缺点:1. 由协调者发起事务时,如果有某个组件不可用会导致,其余组件业务处理导致的资源浪费,因为其余组件会正常执行业务,而故障组件没有反应,等最后协调者没收到执行结果通知,就会发起回滚通知,导致其余正常组件浪费资源
2. 会导致事务的全局阻塞,进而影响系统的性能
2. 3PC:2PC的优化版
新增一个询问操作,在协调者发起事务之前会有一个cancommit询问事务是否正常,由各个组件事务回复正常后,在由事务协调者发起事务,解决了2PC中的缺点一的问题
3. TCC: try,confirm,cancel三个单词简称
try:尝试:在执行前有协调者发起尝试,并确定是否需要预留/冻结资源
confirm:执行:正常执行业务
cancel:取消:当某个组件事务异常后发起
优点:一定程度解决了2pc的缺点2,阻塞粒度更小
缺点:是要在代码中执行这个三方法,代码间的强耦合更高
4:SAGA:长事务,将事务拆分成各个小事务,没办法保证隔离性
由协调者发起事务后,各个组件分别执行各自业务,如果都没有异常的情况,最终就会提交。如果出现异常,那么由事务协调者发起反向回滚,各个组件收到反向回滚提醒后,进行反向操作,已增加的减少,已扣除的添加
5. 最终一致性:MQ队列
1. 由生产者发起业务并发送半消息到mq,生产者执行业务提交数据,并向mq确认消息,由mq向消费者发送mq消息,消费者消费信息,执行业务。如果生产者业务执行失败,会通知mq删除预备消息,如果消费者业务执行失败会进行重试,如果反复重试16次仍失败,可进行人工干预,并且多半可以判断业务代码逻辑存在异常
4. seata和saga
65. 分布式锁常见解决方案
1. 分布式锁目的:使用Synchronized(jvm层面)同步锁无法完成,需要在分布式系统(在多个服务器)互斥的场景
1. 可以解决分布式情况不同节点再执行同一个定时任务
2. 保证在并发情况两个节点操作同一条数据时出现异常,避免了破坏数据的正确性
2. 分布式锁解决方案:
1. mysql:不常用
通过在数据库插入一张锁信息表,每次当有线程执行业务时,进来查找锁信息状态是否被占用,然后再决定是否操作。但是会占用数据库资源,不推荐
2. zookeeper:为每一个请求线程创建一个节点,谁排在前面谁优先获取锁资源,次一级进入监控状态,实时监听锁是否被释放,被释放则可优先获取锁
3. redis:通过注入redissonclient加锁和释放锁
1. 底层原理:
1. 看门狗:线程获取锁执行任务,如果任务所需要的时间大于锁超时时长,此时锁就会被释放,可能导致任务失败,看门狗是为了防止此种情况,在超时时长即将结束时,回去判断任务是否完成,未完成,则增加超时时长延长锁寿命
2. 红锁:在redis集群中,会通过红锁的方式使主从节点都加上锁,所有节点反馈加锁成功后,看门狗才会认为此时加锁成功
public boolean expire(String key, long time){
RLock lock = redissonClient.getLock("DISLOCK");
lock.lock();
try {
if(time > 0){
redisTemplate.expire(key, time, TimeUnit.SECONDS);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}finally {
lock.unlock();
}
}
66. 类的生命周期
加载:由类加载器将class文件加载至内存区
链接:验证:验证字节码格式、准备:静态变量内存空间分配及初始化、解析:符号引用替换直接引用
初始化: 线程需要使用这个类的时候比如说new或者调用这个类的方法变量之类的
使用:正常在被使用
卸载:对象没有被引用后被gc机制回收