JAVA面试宝典(更新)

426 阅读1小时+

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

java基础

1. Object有哪些方法?

hashcode ,equals ,wait(), tostring()

2.hashcode 和eques有什么关系?如果违反其中的规定有什么后果?

Java中规定,hashcode相同equals不一定相同,equals相同那么hashcode一定相同,如果违反这种规则hashMap和hashSet不能正常使用

3.wait和sleep有什么区别?

(1) sleep()方法线程不会释放对象锁,wait()方法线程会释放对象锁

(2) sleep可以在任何地方使用,而wait只能在同步方法或者同步块中使用

(3) sleep是Thread线程类的静态方法,而wait是Object顶级类的普通方法

4.String、StringBuilder和StringBuffer的区别?

(1) String类是不可变类,String对象一旦创建,其值是不能修改的

(2) StringBufferr类是可变类,用synchronized同步了,是线程安全的

(3) StringBuilder类是可变类,是非线程安全的

5.==和equals的区别?

(1) ==比较的是对象地址

(2) equals比较的是对象内容

6.Arraylist和Linkedlist有什么区别?

Arraylist是基于数组的,在查询效率比较高,插入删除效率比较低

Linkedlist是基于链表的,插入删除效率比较高,查询效率比较低

对于添加和删除的时候,linkedlist优于arraylist,因为arraylist在做数据的添加和删除的时候需要有数据的位置的移动

7.List数据去重的几种有效方法?

HashSet去重

JDK1.8的distinct(地思ten特)去重

8.BIO、NIO、AIO的区别?

BIO可以实现聊天,服务端创建一个ServerSocket;客户端用一个Socket去连接服务端的那个ServerSocket;ServerSocket接收到了一个的连接请求,就创建一个Socket和一个线程去跟那个Socket进行通讯。 但是如果高并发场景,创建的线程会达到几千或几万以上,会导致服务端过载过高,最后宕机

BIO:是同步阻塞式,是传统的IO,使用简单,但是没有办法处理并发

NIO:是同步非阻塞,是传统IO的升级(BIO的升级),服务端和客户端通过channel通道,实现一个多路复用

AIO:是NIO的升级,也可以叫NIO2,是异步非阻塞,实现回调机制

9.重载和重写的区别?

重载是:方法名相同参数列表不同

重写是:方法名和参数列表都相同,一般是子类重写父类的方法

10.jdk1.7和jdk1.8有什么区别?

(1) jdk1.8新增了lambda表达式,还有stream流

(2) HashMap底层做了改进。jdk1.7是数组加链表,jdk1.8是数组加链表加红黑树

(3) jdk1.8的接口里面可以写默认的方法

11.对称加密和非对称加密有什么区别?

对称加密:加密和解密必须使用同一个密钥,算法有DES,AES

非对称加密:加密和解密不是同一个密钥,一般有一个公钥,一个私钥,公钥加密,私钥解密,算法有RSA

12.HashMap底层原理?

Jdk1.7是基于数组加链表实现,jdk1.8是数组加链表,长度大于8转红黑树

发生hash碰撞时,java 1.7 会在链表的头部插入,而java 1.8会在链表的尾部插入

13.HashMap 和 HashTable 有什么区别

可以通过几个方面进行回答:线程安全、效率原因、键值null、扩容机制 四个方面回答

HashMap 是线程不安全的,HashTable 是线程安全的;

由于线程安全,所以 HashTable 的效率比不上 HashMap;1

HashMap最多只允许一条记录的键为null,允许多条记录的值为null,而 HashTable不允许;

HashMap 默认初始化数组的大小为16,HashTable 为 11,前者扩容时,扩大两倍,后者扩大两倍 +1

...

14.为什么 ConcurrentHashMap 比 HashTable 效率要高?

HashTable 使用一把锁(锁住整个链表结构)处理并发问题,多个线程竞争一把锁,容易阻塞;

ConcurrentHashMap JDK 1.7 中使用分段锁(ReentrantLock + Segment + HashEntry),相当于把一个 HashMap 分成多个段,每段分配一把锁,这样支持多线程访问。锁粒度:基于 Segment,包含多个 HashEntry。

JDK 1.8 中使用 CAS + synchronized + Node + 红黑树。锁粒度:Node(首结点)(实现 Map.Entry<K,V>)。锁粒度降低了。

15.HashMap 的 table 的容量如何确定?loadFactor 是什么?该容量如何变化?这种变化会带来什么问题

table 数组大小是由 capacity 这个参数确定的,默认是16,也可以构造时传入,最大限制是1<<30;

loadFactor 是装载因子,主要目的是用来确认table 数组是否需要动态扩展,默认值是0.75,比如 table 数组大小为 16,装载因子为 0.75 时,threshold 就是12,当 table 的实际大小超过 12 时, table就需要动态扩容;

扩容时,调用 resize() 方法,将 table 长度变为原来的两倍(注意是 table 长度,而不是 threshold)

如果数据很大的情况下,扩展时将会带来性能的损失,在性能要求很高的地方,这种损失很可能很致命。

16.HashMap到jdk1.8为什么到8转红黑树?

之所以选择红黑树是为了解决二叉查找树的缺陷,二叉查找树在特殊情况下会变成一条线性结构(这就跟原来使用链表结构一样了,造成很深的问题),遍历查找会非常慢

而红黑树在插入新数据后可能需要通过左旋,右旋、变色这些操作来保持平衡,引入红黑树就是为了查找数据快,解决链表查询深度的问题,我们知道红黑树属于平衡二叉树,但是为了保持“平衡”是需要付出代价的,但是该代价所损耗的资源要比遍历线性链表要少,所以当长度大于8的时候,会使用红黑树,如果链表长度很短的话,根本不需要引入红黑树,引入反而会慢。

17.HashMap负载因子为什么选择0.75?

如果负载因子过高,比如1的情况下,虽然空间开销减少了,提高了空间利用率,但是增加了时间的成本

如果负载因子过低,例如0.5虽然可以减少时间的成本,但是空间利率用低

主要是时间和空间做一个权衡

18.Hashmap 与 ConcurrentHashMap区别?

hashmap本质是数组+链表 根据key去获取hash值 然后计算出对应的下标,如果有多个key对应同一个下标,就用链表的形式存储

ConcurrentHashMap在hashmap的基础上 ConcurrentHashMap将数据分成了多个数据段(segment 默认是16) 主要是对segment去加锁

hashmap的键值允许null值,但是ConcurrentHashMap不允许

在jdk1.7 ConcurrentHashMap是由segment数组和hashentry数组结构组成

在jdk1.8 ConcurrentHashMap放弃了segment用node+cas+synchronize保证

ConcurrentHashMap是线程安全, hashmap线程不安全

19.HashMap的线程不安全主要体现在下面两个方面?

1、JDK1.7中,当并发执行扩容操作时会造成环形链和数据丢失的情况

2、JDK1.8中,当并发执行put的时候会对数据造成覆盖的情况

HashMap面试问题mp.weixin.qq.com/s/GxD7ZW-me…

20.单例模式理解?

image.png

image.png

懒汉模式使用的是双重效验锁和 volatile 来保证线程安全的,从上述代码可以看出,无论是饿汉模式还是懒汉模式,它们的实现步骤都是一样的:

怎么创建一个单例?

1.创建一个私有的构造方法,防止其他调用的地方直接 new 对象,这样创建出来的对象就不是单例对象了。

2.创建一个私有变量来保存单例对象。

3.提供一个公共的方法返回单例对象。

懒汉模式相比于饿汉模式来说,不会造成资源的浪费,但写法要复杂一些

Spring

21.为什么用spring?

Spring的核心功能IOC(控制反转,依赖注入),AOP(面向切面的编程)

IOC:我们在使用过程中不用关注于对象是怎么创建的,只用应用过去,sping自动帮我们完成注入,对象的创建,spring默认创建对象是单例,这样减少了频繁创建对象,让对象重复利用,所有的对象都是放在BeanFactory 工厂

(IOC自动注入,自动创建对象,因为是单例,所以可以重复利用,不用频繁创建,放到BeanFactory工厂中)

AOP:面向切面的编程,我们可以把一些公共的东西模块化,做成一个切面,在方法的运行过程中织入进去,好处是解耦,提高代码的重复利用率

22.项目哪些地方用到了AOP?

我们经常使用的事务@Transactional的底层就是aop去实现的,还有日志,权限认证

23.AOP的底层怎么实现的?

Aop底层其实就是通过动态代理去实现的,分为jdk的动态代理和cglib,jdk的动态动态代理必须要实现接口,cglib是以继承的方式子类重写父类的方法增强,spring默认会优先采用jdk的动态代理,如果没用实现接口再采用cglib代理

24.@Transactional(揣塞可申闹)事务什么时候不生效

(1) 方法用private,final修饰的事务不生效(因为事务底层是动态代理,加了后没有办法去重写代理方法增强)

(2) 事务默认针对RuntimeException(诶可拍可申)生效,如果内部抛出的不是RuntimeException不生效

(3) 如果在本类内部调用带有一个事务的方法事务不生效

(4) 对加有@Transactional事务的方法try catch了的不生效(因为异常已经被捕获,相当于没用异常了)

(5)存储引擎是innDB,要存储引擎支持事务

25.Spring Bean的作用域范围?

singleton(单例)(C勾ten)

Prototype(原形范围与单例范围相反,为每一个bean请求提供一个实例)

Request(在请求bean范围内会每一个来自客户端的网络请求创建一个实例,在请求完成以后,bean会失效并被垃圾回收器回收)

Session(与请求范围类似,确保每个session中有一个bean的实例,在session过期后,bean会随之失效)

global-session

26.Spring框架中的单例Beans是线程安全的么?

谈到beans线程安全那么就看bean是否有多种状态,如果始终只有一种状态 就是线程安全的,否则需要自己去保证线程的安全,可以采用将singleton变为prototype

27.SpringMVC原理,执行流程?

(1)客户端(浏览器)发送请求,直接请求到DispatcherServlet(迪斯啪撤)。

(2)DispatcherServlet根据请求信息调用HandlerMapping,解析请求对应的Handler。

(3)解析到对应的Handler后,开始由HandlerAdapter适配器(额大破特er)处理。

(4)HandlerAdapter会根据Handler来调用真正的处理器处理请求,并处理相应的业务逻辑

(5)处理器处理完业务后,会返回一个ModelAndView对象(v有),Model是返回的数据对象,View是个逻辑上的View。

(6)ViewResolver(瑞搜哇)会根据逻辑View查找实际的View

(7)DispaterServlet把返回的Model传给View。

(8)通过View返回给请求者(浏览器)

28.Spring事务传播特性?

A ---------------->  哥哥
B ---------------->  我
执行代码 ------> 读书
事务------------->  吃苹果
挂起事务 ------>  暂停吃苹果
抛异常 ----- --->  妈妈发脾气

Spring 支持 7 种事务传播行为:

PROPAGATION_REQUIRED 如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择。

大白话:哥哥和我,我们两个人每人都有一个苹果,最终我们的苹果会合并成一个苹果一起吃;

PROPAGATION_SUPPORTS 支持当前事务,如果当前没有事务,就以非事务方式执行。

大白话:如果哥哥有一个苹果,我就吃哥哥的苹果,如果哥哥没有苹果,那我也没得吃;

PROPAGATION_MANDATORY 使用当前的事务,如果当前没有事务,就抛出异常。

大白话:不管怎样,我都会得到一个新的苹果,如果哥哥正在吃苹果,那么他吃苹果的动作会先暂停,等我吃完之后哥哥在继续吃;

PROPAGATION_REQUIRES_NEW 新建事务,如果当前存在事务,把当前事务挂起。

大白话:如果哥哥有一个苹果,那么我也吃他的苹果,如果哥哥没有苹果,妈妈就会发脾气;

PROPAGATION_NOT_SUPPORTED 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。

大白话:我总是不吃苹果,如果哥哥正在吃苹果,遇到我正在读书,哥哥会暂停吃苹果这个行为,待我读书读完后,哥哥就会继续吃苹果

PROPAGATION_NEVER 以非事务方式执行,如果当前存在事务,则抛出异常。

大白话:我总是不吃苹果,如果哥哥有苹果,那妈妈就会发脾气

PROPAGATION_NESTED 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与 PROPAGATION_REQUIRED 类似的操作。

大白话:如果哥哥吃苹果吃出毛病了(代码抛异常了),那我和哥哥都会回到原点(回滚),如果我吃苹果吃出毛病了(代码抛异常了),那就只有我会回到原点(回滚);

29.Spring是如何解决循环依赖问题的?

循环依赖就是:A依赖B ,B依赖A ,采用三级缓存

提前暴露的对象,虽然已经实例化,但是还没有完成实例化,是一个不完整的对象,这个对象存放在二级缓存中,对于三级缓存十分重要

  1. 调用doCreateBean()(度酷睿特彬),由于还未创建,从一级缓存singletonObject查不到,此时只是一个半成品(提前暴露的对象),放入第三级缓存singletonFactories。(森钩ten fai克特瑞字) 2.A在属性填充时发现自己需要的B对象,但是在三级缓存中均未发现B,于是创建B的半成品,放入第三级缓存SinglotonFactories。 3.B在属性填充时发现自己需要A对象,从第一级缓冲singletonObject和第二级缓存earlySingletonObject中未发现A,但是在第三级缓存singletonFactories中发现A,将A放入第二级缓存earlySingletonObject(额嘞森钩ten),同时将第三级缓存singletonFactories删除。 4.将A注入到对象B中。 5.B完成属性填充,执行初始化方法,将自己放入第一级缓存SingletonObject中(此时B是一个完整的对象),同时从第三级缓存singletonFactories 和第二级缓存earlySingletonObject中删除。 6.A得到“对象B的完整实例”,将B注入到A中。 7.A完成属性填充,执行初始化方法,并放到第一级缓存singletonObjects中。

30.Spring Boot 的底层原理?

Spring Boot 底层实际上主要的有三个注解:Configuration ,EnableAutoConfiguation,,ComponentScan(肯剖嫩特),它会读取META-INF下的spring.factories(fai可特瑞斯,工厂)文件信息,通过反射的机制把bean纳入到spring的管理

31.Spring有哪些自动装配的方式?

byName:根据名称进行自动匹配

byType:根据类型进行自动匹配

constructor(肯斯戳科特):构造函数注入

autodetect(迪泰克特):根据Bean的自省机制决定采用byType还是constructor进行自动装配,如果Bean提供了默认的构造函数,则采用byType,否则采用constructor

32.@Autowired(凹凸外的)和@Resource(瑞骚死)注解的区别是什么?

Autowired是通过bean类型注入的,如果一个接口有多个实现,那么采用Qualifier (阔类fai奥)配合使用,Resource是通过bean名称注入的

33.Spring中BeanFactory和FactoryBean的区别?

BeanFactory:它是一个bean工厂,spring把对象创建好后都会放入这个工厂,底层存储对象其实就是一个大的ConcurrentHashMap存储的对象实例

FactoryBean:它是一个特殊的bean,实现了这个接口,可以通过getObject获取到自定义的bean

34.Spring的Bean的生命周期

通过refresh方法里面完成bean的创建,先会去创建BeanFactory,然后扫描包转换成beanDefinition(Bean呆非内申),通过后置处理器完成,实例化,属性赋值,初始化,销毁。

每个阶段可以实现不同的接口,如:初始化的时候实现InitializingBean(in_诶申_赖zing Bean)接口,调用afterPropertiesSet()(p绕per忒思) 方法,销毁的时候实现DisposableBean(dis_pos_able_Bean)接口,调用destroy() (dis赵诶)方法

35.Spring Cloud 组件?

Eureka 注册中心

ribbon:负载均衡策略

hystrix:熔断器(黑丝吹科斯)

zuul:网关

config:配置中心

36.springboot和springcloud区别?

springboot主要快速开发整合包,他主要是方便单个的微服务,而springcloud是治理框架,将每个单个的微服务结合起来,并且为他们提供配置,服务发现,路由等集成服务

37.springcloud负载均衡策略?

随机,轮询,最小使用数,权重

38.网关的作用是什么?

统一的请求路由,权限认证,安全校验,限流

39.Spring Cloud断路器的作用

一个服务在调用另一个服务的时候由于网络的原因出现了问题,

调用者会一直等待被调用的者的响应。当更多的请求要调用,就会出现更多的请求等待。

一段时间内,没有得到被调用者的响应,多次检测没有恢复的迹象,这个时候断路器完全打开;

断路器半开状态:有恢复的迹象,将部分的请求发给服务;

如果正常调用没有出现等待请求那么处于断路器关闭。

40.什么是Spring Cloud Config

分布式系统中,服务数量比较多,要想实现一个统一管理,实时更新,需要一个配置中心组件,在springcloud中用的是springcloudconfig去实现的,他是可以放在内存中也可以放在git仓库中的,主要有两个角色一个是config server和config client 使用就是

1、加pom依赖 2、相关的配置文件,3、启动类加enableconfigserver

41.什么是Spring Cloud Gateway

springcloud的gateway是网关,取代了zuul网关,在微服务中有重要的作用,常见的功能有路由,权限验证,限流控制具体是route(入他)去处理的,filters(非_u_车er)是各种过滤器。

42.什么是springcloud的熔断机制

熔断器是起了保护的作用,和保险丝一样,到了某个结点就会进行熔断保险丝保护电路,springcloud中的熔断机制也是如此,如果某一服务发生了崩溃,那么要进行熔断机制防止整个系统都崩溃

43.Eureka和ZooKeeper区别

  • eureka是基于AP设计的,zookeeper是基于CP设计的

    c:一致性 a:可用性 p:分区容错性

  • ZooKeeper有Leader(利der)和Follower(发了我er)角色,Eureka各个节点平等

  • eureka可以很好的解决出现故障导致的部分结点失去联系,zookeeper如果出现故障整个服务器都会瘫痪

  • ZooKeeper只是一个进程,Eureka本质上是一个工程

  • zookeeper采用半数存活原则,eureka采用自我保护机制解决分区问题

44.Eureka保证AP

eureka保证了可用性,并且各个节点都是平等的,几个结点挂掉是不会影响正常工作的,如果eureka的客户端注册失败的话,会自动切换其他节点,只要还有一台eureka还在的话就能保证可用性,eureka还有自我保护机制,如果超过了15分钟85%的结点都没有心跳了,那么就认为客户端出现了网络故障

45.Zookeeper保证CP

zookeeper保证了一致性,但是我们在注册信息的时候是没有办法容忍服务器宕机的。也就是说服务的可用性要高于一致性。zookeeper会出现一种情况:当主节点因为网络原因与其他的结点失去联系的时候,剩余的结点会进行一个leader选举(利der),但是选举的过程中时间很长的可以达到30—120秒,在选举的过程中zookeeper是没有办法使用的,在云部署的情况下zookeeper主结点丢失这种情况的概率是很大的,虽然可以恢复但是时间长

46.Eureka的底层原理

eureka主要是通过心跳检测去判断的,有一个发送者和客户端,发送者会每隔30秒发送一个心跳到eureka上去,服务端会把发送的数据进行接收并调用,如果说生产者没有发送心跳到注册中心上,那么就将所有的接口直接剔除掉,如果说中途eureka发生了宕机,那么也是可以进行调用的,因为将原来的数据放到了缓存中去,并且eureka还有自我保护机制,如果说在15分钟内检测到有85%的服务都宕机了那么这个时候就会认为是网络的问题导致的

47.Feign 的底层原理

底层基于动态代理发送http请求,然后通过负载均衡算法调到对应的服务机器

48.hystrix(黑丝吹渴死)实现原理

  • 隔离(线程隔离和信号量隔离)

  • 熔断

  • 降级

  • 缓存 将请求放到缓存中

隔离:线程隔离:主要是交给线程去做处理的,每个请求都要交给线程去做处理,是可以处理突发情况的(因为如果没有及时处理的数据是要放到队列中去,一点一点执行的),是异步的

信号量隔离:主要是采用原子计时器(信号量)去对请求做处理的,如果说发送一个请求,当前线程数已经大于最大线程数,那么就会拒绝,不接受请求,如果没有就计数器+1,当返回结果后在进行计数器-1,这个是立即返回给用户的,没有办法处理突发情况,是同步

熔断:如果说长时间没有返回给客户请求响应,那么会进行熔断开启状态,这个时候会拒绝所有的请求,过段时间会进入熔断半开状态 要接收一部分请求做出响应。

降级:忍痛割爱 要把一些服务器先停掉,等到执行完之后再将其打开

Mybatis

49.Mybatis的一级、二级缓存?

一级缓存存储的作用域是session,当session flush(弗拉士)或者close(可篓子)之后,session中的缓存会失效,此时一级缓存就开启

二级缓存和一级缓存机制相同,hashmap存储,作用域为mapper,二级缓存一般不用,因为他不好控制缓存的刷新,我们一般是用的是redis

50.MyBatis 的接口mapper可以重载么?

不可以,mybatis查找mapper内的方法是靠方法名,和参数无关。所以,对于mapper接口,Mybatis禁止方法重载

51.Hibernate 和 MyBatis 的区别?

mybatis是一个小巧方便,半自动化持久层框架,学习起来比较容易

hibernate是一个全自动化持久层框架,做sql优化难,学习也比较困难

52.#{}和${}的区别是什么?

1)#{}是预编译处理,效率要高点,${}是字符串替换。

2)#{} 可以有效的防止SQL注入,提高系统安全性。${}有sql注入的风险

多线程

53.实现线程的方法有哪些?

1、创建thread方法

2、实现runable接口

3、实现callable接口(可以获取线程的返回结果)

54.runable和callable的区别?

1、runable是实现run(),callable是实现call()

2、runable无返回值,callable有返回值

3、runable不抛异常,callable抛异常

55.如何让线程的有序执行?

采用join

countdownlatch类,首先有countdown()和await(),当调用countdown()时,计数器不为0的时候就会执行await(),如果技术器为0则会执行下一个任务

采用 Executors的单线程池创建方式 Executors.newSingleThreadExecutor();

56.如何获取线程的返回结果?

实现callable接口,返回Futrue,通过get方法可以获取结果

57.线程数过多会造成什么情况?

1、消耗资源

2、占用cpu

3、降低了稳定性

58.解决线程安全的方式有哪些?

1、加synchronized锁

2、加lock锁 此时采用的是Reentratlock锁 (瑞踹特捞克)

3、用ThreadLocal

4、atomic(额套门课)

5、提供了一些线程安全的类比如ConcurrentHashmap(抗卡润特哈希麦破)

59.线程池的底层原理

创建线程池的时候,开始一个线程也没有,随着任务的提交创建线程,当前线程如果小于corePoolSize核心线程数,创建线程, 否则(当前线程大于或等于核心线程数)放入LinkedBlockingQueue队列,如果队列没有满,继续放入,如果队列满了, 判断是否小于最大线程数,如果小于继续创建线程,否则拒绝策略(默认拒绝策略抛异常)

核心线程数----阻塞队列----最大线程数

60.拒绝策略有哪些?

1、使用线程解决

2、直接拒绝不抛异常

3、直接拒绝抛出异常(默认)

4、将最早的线程舍弃,将最新线程添加

61.创建线程池的方法有哪些?

1、单一线程池(始终只有一个线程)

2、定时线程池(在固定时间段内使用线程)

3、定长线程池(自定义设置线程大小 一般使用)

4、可缓存线程池(无限制的线程大小)

62.synchroized的底层原理

synchroized是通过监视器monitor来完成的,如果monitor被占用时会处于死锁的状态,线程需要 执行monitorenter指令去尝试获取monitor的所有权,如果monitor的进入数为0,那么进入现场进入monitor,然后将进入数设置为1,此线程为monitor的所有者,如果线程已经有monitor需要重新进入,monitor为+1,如果已经占用了monitor,则该线程进入等待的状态,直到monitor的进入数为0时,再去重新获取所有权。

63.ReentratLock的底层原理,存储结构,获取锁的过程,释放锁的过程

ReentratLock主要是通过AQS+CAS来实现的

先通过CAS去获取锁,如果锁获取到了就将线程放入AQS队列中去,如果锁释放了,就会释放AQS队列中的首个线程,在通过CAS去尝试获取锁。

底层是基于AQS实现的,每个都有自己的内部类

lock的存储结构:一个int类型(用来存放锁的状态变更) 一个是双向链表(用于存储等待中的线程)

lock获取锁的过程:本质上是通过CAS来修改状态,如果获取到锁之后就更改锁的状态,没有获取到锁就放到等待链表中进行等待。

lock释放锁的过程:修改状态,调整链表

64.ThreadLocal原理?

当多个线程操作同一变量且互不干扰的情况下,可以使用threadlocal来解决,它会每个线程都创建一个副本,线程内部都会创建一个变量。在底层有一个巨大的map来存放线程,如果在使用完线程之后没有释放,那么会造成一个内存溢出的情况

65.ThreadLocal为什么会造成内存溢出?

因为底层是巨大的map,而map的key是弱引用,value是强引用,而进行回收的时候可以回收掉key,value是回收不掉的,解决方法可以直接使用自带的remove

66.volatile 有什么作用?

  1. 防止指令重排序,2.保证各个线程之间的可见性

67.AQS底层原理

AQS维护volatile下的共享资源state,如果获取到锁了那么就去改变state的状态,如果没有获取到那么就进入一个阻塞的状态,放入阻塞队列中去

原理就是:AQS实际是通过CLH队列操作的,CLH队列是一个虚拟的双向队列,通过CAS去获取,获取到修改state状态,获取不到就放入阻塞队列中去

68.CAS原理

CAS(compare and swap)是比较交换,是一种乐观锁, VAB V是内存预期要修改的变量,A是要修改的预期值,B是新值,如果用户要去改变内存中的值,那么A要去和V去做对比,如果相等了那么B的值就是A的值。

会出现一个ABA的问题,如果有一个人吧值从原来的1改成了2,后又吧2改成了1,那么别人认为这个操作是无用的,其实对于CAS来说是用的,就会出现ABA的问题,解决方式 每修改一次就给版本号上+标识

69.synchronized 和 lock 有什么区别?

synchronized 是java语言的关键字,lock是一个类,通过这个类可以实现同步访问

synchronized我们一般是锁代码块,lock一般是我们需要创建一把锁,然后执行任务。最后需要手动释放锁

synchronized的底层是基于JVM指令完成,它会在代码块中加入monitorenter和monitorexit执行,而lock是通过代码实现的,底层基于AQS实现的

synchronized 获得锁和释放锁的方式都在块结构中,是 自动释放锁,lock需要手动释放,并且必须在finally中,否则会造成死锁

Mysql数据库

70.事务的特性

原子性(支持回滚,底层根据undo log,事务如果中途出现错误,会进行回滚,回滚到开始前的状态)

一致性 (事务开始前和结束后,数据库的完整性约束没有破坏)

隔离性(只允许一个事务请求同一数据,不同事务之间彼此互不干扰,底层MVCC多版本并发控制间隙锁--读写锁)

持久性 (不支持回滚,底层根据 redo log)

71.事务的并发问题(脏读、幻读、不可重复读)

脏读:事务A已经更新了一份数据,在这个过程中,事务B去执行了同一份数据,但是由于某些原因,被修改的数据rollback了,然后一个事务所读取的数据就不一样了(没有提交,进行了回滚)

不可重复读(一个事务中不允许多次读取数据):事务a多次读取同一个数据,事务B在事务A多次读取的过程中对数据做了更改,导致最终事务A读的数据不一致(提交成功了)

幻读:管理员A已经把学生的信息全部统计完毕了,在统计过程中,管理员B添加了一条数据,但是管理员A不知道,等管理员A执行完之后,发现有一条数据没有被统计进来,这个时候就发生了幻读(提交成功了)

不可重复读和幻读的区别:不可重复读是侧重于修改,幻读侧重于新增或删除,解决不可重复读就锁住满足条件的,解决幻读是需要锁全表

72.MySQL事务隔离级别

读未提交(脏读、不可重复读、幻读)事务A读取到事务B未提交的数据

读已提交(不可重复读、幻读)事务A去修改数据但是不提交,事务B查询数据查询的还是原来的数据,事务A提交事务,事务B再次读取数据,读到的数据和第一次读取 的数据是不一致的

可重复读(幻读 默认的)事务A在执行的过程中不会读取到其他提交的事务,只有当前事务结束之后才可以读取到

串行化 排队依次去执行

73.binlog、redo log和undo log

bin log:读写分离,每次操作都会记录到binlog中去,可以直接在binlog中去查询丢失的数据

dfff2ccf36c61a330d929ca2b4a2bae.png

redo log:保证持久性

undo log:保证原子性

74.MySQL事务实现原理,MVCC的原理,事物的隔离性,间隙锁的原理

主要是用undo log和redo log来实现的。

undo log用来恢复数据,保证了原子性

redo log用来回滚数据,保证了持久化

事务的隔离性是通过(读写锁(间歇锁)+MVCC)来实现的

MVCC的原理:将历史信息在快照内存中,其他事务发生删除修改操作,都是对他不可见的

间隙锁的原理:读取数据的该行与上一行和下一行有一个间隙的锁定,保证在此范围内读取到的数据是一致的

75.left join、right join和inner join区别?

left join(左连接) 返回包括左表中的所有记录和右表中联结字段相等的记录

right join(右连接) 返回包括右表中的所有记录和左表中联结字段相等的记录

inner join(内连接) 只返回两个表中联结字段相等的行

76.说一下 mysql 的行锁和表锁

mysiam支持表锁,innodb支持行锁

表级锁:开销小,加锁快,不会出现死锁。锁定力度大,并发量最低,发生锁冲突的概率最高

行级锁:开销大,加锁慢,会出现死锁。锁定力度小,并发度最高,发生锁冲突的概率小

索引

77.索引有哪些

主键索引,唯一索引,普通索引,组合索引,全文索引

78.数据结构

数组:查询效率比较高,添加,删除效率比较低

链表:删除,添加操作效率比较高,查询效率比较低

hash:做的是数据的比较,不能做区间查询 等值查

79.二叉树:

二叉树是每个节点最多有两个子节点的树。

二叉树的叶子节点有0个字节点,二叉树的根节点或者内部节点有一个或者两个字节点。

二叉树在极端的情况下会出现单链表的情况,所以说查询的速度还是慢

红黑树:根节点永远是黑色的

80.红黑树与AVL树的比较:

AVL是严格的平衡树,因此在增加或者删除节点的时候,根据不同情况,旋转的次数比红黑树要多;

红黑树是用非严格的平衡来换取增删节点时候旋转次数的降低开销;

所以简单说,如果你的应用中,搜索的次数远远大于插入和删除,那么选择AVL树,

如果搜索,插入删除次数几乎差不多,应选择红黑树

B树(b-)一个节点可以存多个数据块,它的高度降低了很多,顶多达到3层高度,高度变小,IO次数变少,性能有提升

B+树(在B树上做了改造)

81.Innodb和Myisam存储引擎区别

1.索引区别,Innodb聚集索引(数据文件和索引文件放在一起的)

Innodb二级索引(非主键的索引)(建立普通索引和主键的关系,先通过普通索引找主键,然后回表找数据)

Myisam非聚集索引(数据文件和索引文件分开的)

Myisam二级索引(跟主键没有关系,都是维护独立索引树,然后叶子节点存地址,然后通过地址找数据)

2.Myisam不支持事务的,Innodb支持事务

3.Myisam表级锁,Innodb是行级锁(必须使用到索引列才能去使用)

4.Myisam崩溃后无法安全恢复 ,Innodb具有自动崩溃恢复功能

82.uuid为什么不适合做主键?

32位,空间占用率大,无序,Innodb索引文件会很大

因为我们数据库通常Innodb用存储引擎,它的索引结构是根据主键去组织的,那么占用空间会很大,并且我们建了二级索引, 二级索引它会和主键索引建立关系,先去二级索引查询主键,然后通过主键再去查询数据块(回表 )

因为无序,没法充分利用B+树特性,非叶子节点没法充分使用操作系统系统空间局部性原理,导致性能低

83.1万数据未支付,已支付,支付失败状态3种,适合建索引么?

不适合,建索引规则必须要保证离散型最强的一列,不然重复比较多

84.sql优化

-- 全表扫描 EXPLAIN select from tb_test;

EXPLAIN select FROM tb_test where mobile='15921484451';

EXPLAIN select FROM tb_test where mobile=15921484451;

数据库有隐式类型转换,索引类型不一样,不走索引

EXPLAIN select FROM tb_test where mobile like '%21%';

EXPLAIN select FROM tb_test where mobile like '134%';

like查询前模糊匹配不走索引,like后模糊匹配可能会走索引

组合索引user_name与mobile

EXPLAIN select FROM tb_test where user_name='大宝' and mobile='15921484451';

EXPLAIN select FROM tb_test where user_name='大宝' ;

EXPLAIN select FROM tb_test where mobile='15921484451';

85.Sql优化不走索引的情况?

1.数据库有隐式类型转换,索引类型不一样,不走索引

2.like查询前模糊匹配不走索引,后模糊匹配可能会走索引

3.NULL列不走索引

4.not,not in ,!=,or不走索引

5.在建的索列上有函数操作,都不走索引,比如EXPLAIN select FROM tb_test where substr(mobile,1,3)='159'

6.组合索引必须满足最左匹配原则,比如:abc,a,ab,abc,ac

86.索引覆盖

建了user_name和mobile对应的组合索引

select from tb_user_test where user_name='大宝' and mobile='15921484451';

select user_name,mobile from tb_user_test where user_name='大宝' and mobile='15921484451';

如果查询的列只包含我们建的索引列,此时不需要回表操作,叫索引覆盖

87.回表

Innodb是聚集索引,它的索引结构是根据主键去组织的,二级索引它会和主键索引建立关系,先去二级索引查询主键,然后通过主键再去查询数据块的这个过程

88.mysql深度分页问题

在查询的时候,如果查询的数据量较大的情况下,用limit查询的时候,查询的效率会慢很多,如:

select from tb_user limit 3000000,10;

优化方案:

1.如果表有自增id,最好加上一个条件,id>,取上一页的id加上id>上一页id,可以提高效率

2.如果没有自增,可以用延迟关联处理

因为会有一个回表的操作,所以说可以先查询出他们的id,查询出来之后然后在去查询数据,通过inner join去连接

89.LIux命令

cd 切换目录

pwd 查看当前路径

mkdir 创建目录

mv 修改目录名称

ps -ef|grep java 查看java进程

kill -9 进程号 杀掉进程

tail -400f 文件名称 查看最后400行

tail -50f ./zookeeper.out |grep '/usr/local/src/java/' 搜索zookeeper.out日志包含的/usr/local/src/java/

vi 编辑文本

cat 查看文件

find文件

chmod 777 ./start.sh 给star.sh赋所有权限

top 查看服务器资源,比如内存,cpu使用情况

消息队列

什么是消息队列?

消息队列是使用队列来通信的组件,就是个转发器,包含发消息,存消息,消费消息的过程。

我们通常说的消息队列 简称为 MQ 其实就是消息中间件。

现在业界流行的开源的消息中间件包括 RabbitMQ RocketMQ Kafka.

消息堆积问题

由于消费者速度小于生产速度

解决方案:不保证顺序的情况下,可以使用多线程来解决

保证顺序的情况下,可以使用多个队列对应多个线程

90.RabbitMQ的死信队列和延时队列?

消息被拒,requeue设置 为 false

消息过期

队列达到最大程度 这时候会存放到死信队列中去。

设置消息过期时间:采用队列中的x-message-ttl 参数去设置,单位是毫秒

91.为什么使用消息队列?

异步(发送手机验证码),

解耦(借款系统和风控系统),

削峰(秒杀,购买商品,放入消息队列排队)

92.使用消息队列有什么缺点?

(1).多了一个MQ服务,可能会出现单点故障,导致系统不可用(集群)

(2).消息队列重复消费幂等问题(一个操作无论执行多少次,都会自己业务没有任何影响)

(3).消息队列丢失问题

(4).消息队列顺序问题

93.消息队列如何选型?

ActiveMQ集群模式很复杂,它的集群模式是分片的,每个机器上只存了部分数据,万一服务挂了,数据就丢了,最高并发10万以内,社区活跃度比较低,对开发的系统安全有影响

RabbitMQ集群模式,可以保证服务挂了,不丢消息,最高并发10万以内,社区活跃度比较高

RocketMQ集群模式,可以保证服务挂了,不丢消息,最高并发10万以上,社区活跃度比较高

Kafka(集群模式,打点统计,日志统计),高并发100万

94.如何保证消息队列是高可用的?

集群

95.如何保证消息不被重复消费?

根据业务场景做幂等(1.比如唯一的信息我们可以建唯一索引,2.监听里面做校验幂等操作,3.每个消息分配一个唯一的id,uuid,消费完redis存一下,然后后面每次都进来校验下)

96.如何保证消息的顺序性?

1.保证一个生产者对应着一个消费者

2.监听器里面把消息消费放到JVM队列(LInkedBlockQueue),然后再消费本地队列

97.如何保证消息不丢失?

MQ发送消息到消费的整个过程分为3个阶段,生产者-MQ-消费者,

1.保证MQ服务的高可用,做集群,持久化

2.消费者在消费MQ里面的内容的时候,如果MQ保存了,MQ会有重试机制,重试还失败后会进入死信队列,然后消息丢失,这个过程可以采用ACK确认机制,手动签收消息,如果消费失败,让消息还是在MQ里面

如果消息在生产者发送到MQ的过程中,因为通讯网络问题,也可能会丢失,这个时候需要做消息持久化,我们会把消息存入到数据库,如果消费成功,把消息删除掉,当然删除消息可能会失败,后面我们可以通过定时任务轮询做补偿,然后继续忘消息队列里面发消息,那么这样可能会出现一个新问题,消息的重复消费,需要考虑幂等问题,我们消息持久化的时候都会分配一个msgId唯一标识,后面消费完了存入redis,消费之前校验一下就可以了

Redis

98.为什么使用redis?

1.支持高可用,3.0集群

2.丰富的数据类型

3.完全内存操作,速度快,支持持久化

4.存储数据大,单个key和value可以存储到1G

99.Redis 的持久化方式?

redis持久化方式有RDB和AOF ,RDB是方式是每过几秒保存的是redis数据的快照,但是可能会丢数据,AOF 保存的是所有在redis执行的命令,它会追加到一个文件里面,丢数据可能性小,但会导致文件很大,假如redis宕机了,恢复的时候会很慢,我们一般使用RDB,因为我们对redis的定位就是缓存服务器,很重要的数据我们不会存redis,比如与钱有关系。

100.redis有哪几种数据类型

string ,hash,list,set,zset

101.redis分布式锁底层原理?

redis分布式锁其实就是往redis设置一个key和value同时设置一个有效时间,并且redis是单线程的,不会并发操作,(执行任务完成后),再把redis的key删除掉(解锁),但是在使用的过程中可能有2个问题,使用过程中我们必须保证设置key和value和设置时间保证它的原子性(LUA),另外还是锁超时问题,比如:上锁2秒钟,但是任务执行超过2秒,我们一般用redission框架,它底层是lua脚本实现,可以保证设置值和时间的原子性,另外还有

看门狗的机制,watch dog,我们上锁3秒,但是任务执行5秒,它会自动加时间

102.为什么使用分布式锁?

因为我们的系统是分布式的,synchronized和lock锁只能是JVM级别的,这个时候需要分布式锁,它实现的思路: redis分布式锁其实就是往redis设置一个key和value同时设置一个有效时间,并且redis是单线程的,不会并发操作,(执行任务完成后),再把redis的key删除掉(解锁),这个过程需要注意的点是,必须保证设置key和value和设置时间保证它的原子性,不然可能出现死锁,另外一方面就是锁任务自动超时问题,所有我们一般用的redisson框架,它完美的帮我们封装了分布式锁,底层是基于LUA脚本实现保证原子性,同时redission

还有watch dog,比如我们上锁3秒,但是任务执行5秒,它会自动加时间

1.分布式锁{

redis查商品,3

校验随机码,

检验秒杀时间段,

商品id和随机码是不是一样,

检验每人限购数量,

检验库存,3

锁定库存(减库存)

下单消息队列

}

103.redis集群模式?

1.主从复制(缺点,主挂掉后,需要人工切换干预,不能保证服务的高可用)

2.哨兵模式(一主多从,主挂掉后,会自动选举主再提供服务,缺点:1.极端情况下网络不是很好的情况,选举需要花时间,可能服务不可用。2. 资源浪费,Redis 数据节点中 slave 节点作为备份节点 )

3.集群模式( Redis Cluster 是 3.0 版后推出的 Redis 分布式集群解决方案 )

  1. 数据按照 slot 存储分布在多个节点,节点间数据共享,可动态调整数据分布。

  2. 可扩展性:可线性扩展到 1000 多个节点,节点可动态添加或删除。

\3. 高可用性:部分节点不可用时,集群仍可用。通过增加 Slave 做 standby 数据副本,能够实现故障自动 failover

104.redis哨兵(Sentinel)模式?

redis节点之间通过心跳检测,从服务器(slave)每过一段时间向主服务器(master)发送一个ping命令,主服务器的响应,如果主服务器挂了,所有从服务器通信,有一种算法选举为主,再提供服务

105.redis cluster原理?

哨兵模式基于主从模式,实现读写分离,它还可以自动切换,系统可用性更高。但是它每个节点存储的数据是一样的,浪费内存。因此,在Redis3.0后Cluster集群应运而生,

它实现了Redis的分布式存储。对数据进行分片,也就是说每台Redis节点上存储不同的内容,来解决在线扩容的问题。

Redis的集群模式本身没有使用一致性hash算法,而是使用slots插槽 。

redis cluster模式采用了无中心节点的方式来实现,每个Master节点都会与其它Master节点保持连接。节点间通过gossip协议交换彼此的信息,同时每个Master节点又有 一个或多个Slave节点;

客户端连接集群时,直接与redis集群的每个Master节点连接,根据hash算法取模将key存储在不同的哈希槽上; 在集群中采用数据分片的方式,将redis集群分为16384个哈希槽。如下图所示,这些哈希槽分别存储于三个Master节点中: Master1负责0 ~ 5460号哈希槽 Master2负责5461 ~ 10922号哈希槽 Master3负责10922 ~ 16383号哈希槽

  1. 每个节点会保存一份数据分布表,节点会将自己的slot信息发送给其他节点,节点间不停的传递数据分布表;

redis集群有多少台

“redis集群规定至少有3台master,因此最少需要6台redis主机。”

Redis值 I/O多路复用模型实现原理

Redis 的 I/O 多路复用模型有效的解决单线程的服务端,使用不阻塞方式处理多个 client 端请求问题。在看 I/O 多路复用知识之前,我们先来看看 Redis 的客服端怎么跟客服端建立连接的、单线程 socket 服务端为什么会存在 I/O 阻塞。

为什么 Redis 中要使用 I/O 多路复用这种技术呢?因为 Redis 是跑在「单线程」中的,所有的操作都是按照顺序线性执行的,但是「由于读写操作等待用户输入 或 输出都是阻塞的」,所以 I/O 操作在一般情况下往往不能直接返回,这会导致某一文件的 I/O 阻塞导,致整个进程无法对其它客户提供服务。而 I/O 多路复用就是为了解决这个问题而出现的。「为了让单线程(进程)的服务端应用同时处理多个客户端的事件,Redis 采用了 IO 多路复用机制。」

redis 6.0 之后引入多线程 为什么?

单线程:

只能使用CPU一个核; 如果删除的键过大(如Set类型有上百万个对象),会导致服务端阻塞好几秒; QPS难再提高。

多线程:

  • 优化网络 I/O 模块

  • 提高机器内存读写的速度

  • 利用多核优势

  • 从Redis自身角度来说,因为读写网络的read/write系统调用占用了Redis执行期间大部分CPU时间,瓶颈主要在于网络的 IO 消耗, 优化主要有两个方向:

    • 提高网络 IO 性能,典型的实现比如使用 DPDK 来替代内核网络栈的方式 • 使用多线程充分利用多核,典型的实现比如 Memcached。

    协议栈优化的这种方式跟 Redis 关系不大,支持多线程是一种最有效最便捷的操作方式。所以总结起来,redis支持多线程主要就是两个原因:

    • 可以充分利用服务器 CPU 资源,目前主线程只能利用一个核 • 多线程任务可以分摊 Redis 同步 IO 读写负荷

redis过期策略

定期删除+惰性删除

定期删除,指的是 redis 默认是每隔 100ms 就随机抽取一些设置了过期时间的 key,检查其是否过期,如果过期就删除。

惰性删除 :key过期的时候不删除,每次从数据库获取key的时候去检查是否过期,若过期,则删除,返回null。

内存淘汰机制

  • allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的 key(这个是最常用的)。
  • allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个 key,这个一般没人用吧,为啥要随机,肯定是把最近最少使用的 key 给干掉啊。

volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的 key(这个一般不太合适)。 volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个 key。 volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的 key 优先移除。

redis可以做什么?

1.缓存,毫无疑问这是Redis当今最为人熟知的使用场景。在提升服务器性能方面非常有效;

2.排行榜,如果使用传统的关系型数据库来做这个事儿,非常的麻烦,而利用Redis的SortSet数据结构能够非常方便搞定;

3.计算器/限速器,利用Redis中原子性的自增操作,我们可以统计类似用户点赞数、用户访问数等,这类操作如果用MySQL,频繁的读写会带来相当大的压力;限速器比较典型的使用场景是限制某个用户访问某个API的频率,常用的有抢购时,防止用户疯狂点击带来不必要的压力;

怎样实现redis高可用?

高可用有两个含义:一是数据尽量不丢失,二是保证服务尽可能可用。 AOF 和 RDB 数据持久化保证了数据尽量不丢失,那么多节点来保证服务尽可能提供服务。

Redis-cluster没有使用一致性hash,而是引入了哈希槽的概念

一般在实际生产中,服务不会部署成单节点,主要是有三个原因.

  1. 容易出现单点故障,导致服务不可用
  2. 单节点处理所有的请求,吞吐量有限
  3. 单节点容量有限

106.redis脑裂问题?

redis集群由于网络的原因可能会出现脑裂的问题,脑裂就是因为主服务器、从服务器和哨兵不在同一个网络中,导致哨兵没有及时的检测到主服务的心跳,在这个时候会在从服务器中去选举一个新的主服务器,这样就有两个主服务器了就像大脑分裂一样,但是这样会导致客户依旧 在旧的主服务器中去写东西,而新的主服务器中没有东西。当网络恢复后,哨兵会把旧的主服务器变成从服务器,这个 时候在去同步数据,可能会造成一个数据的丢失

解决方法:

redis中需要加入两个配置

(旧版本)

      min-slaves-to-write 3 最少有3个从服务器

      min-slaves-max-lag 10 数据复制和同步的延迟不超过10秒

    (新版本)

      min-replicas-to-write 3

      min-replicas-max-lag 10

如果加了这两个配置的话,原来的主服务器当客户端再次进行写操作的时候会拒绝接受,此时就发送到新的主服务器中去了

107.什么是缓存和数据库双写不一致?怎么解决?

数据的信息和缓存由于并发或者其中一个失败导致不一致

解决方案:我们一般是先修改数据库,再删除缓存,因为我们对redis的定位是缓存,redis可能会丢数据,首先保证我们的数据库必须更新,如果redis删除失败,我们采用补偿策略,比如错误了或失败了,把信息放MQ,做消费补偿

108.雪崩?

缓存雪崩是指:由于缓存中的数据一下子全部都在同一时间过期了,所以发送过来的全部请求都去请求数据库,导致数据库难以承受而宕机。

解决方法:

  • 可以保证 redis高可用,建集群;

  • 设置不同的过期时间,防止全部在同一时间过期

假设A请求每秒处理5000个请求 ,但数据库每秒处理4000个请求,假设某一天缓存机器宕机了,

这是所有的请求都打在了数据库中,导致数据库扛不住宕机。

  • 事故前:redis高可用方案,主从+哨兵,集群方案,避免全盘崩溃
  • 事故中:较少数据库的压力,本地Ehcache缓存+限流及降级,避免超过数据库承受压力
  • 事故后:做redis持久化,一旦Redis重启,可从磁盘中快速恢复数据

109.穿透?

缓存穿透是指:缓存和数据库中都没有数据,如果有人恶意访问的会先去缓存查询,此时缓存中无数据,后在去数据库中去查询,数据库中也没有,这个时候就会导致数据库宕机

解决方法:

可以在缓存中设置一个null值,让恶意的请求不会直接击垮数据库,每次访问的时候都去访问此缓存。

可以设计一个过滤器,常用的就是布隆过滤器(可以缓解,为什么是缓解,因为使用过滤器还会造成误判的情况)

110.击穿?

缓存击穿是指:一条数据在查询的时候突然过期了,那么就所有的请求都打在数据库上

解决方法:

加锁,只让一个人去访问数据库并且将访问的数据存入缓存中,供其他的请求来访问

拓展redis

Redis主从复制

  • 从节点执行 slaveofmasterIP,保存主节点信息。
  • 从节点中的定时任务发现主节点信息,建立和主节点的 Socket 连接。
  • 从节点发送 Ping 信号,主节点返回 Pong,两边能互相通信。
  • 连接建立后,主节点将所有数据发送给从节点(数据同步)。
  • 主节点把当前的数据同步给从节点后,便完成了复制的建立过程。接下来,主节点就会持续的把写命令发送给从节点,保证主从数据一致性。

redis跳跃表实现

image.png

  • 跳跃表是有序集合zset的底层实现之一。
  • 跳跃表就是在链表的基础上,增加多级索引提升查找效率。

redis为什么快

  1. 完全基于内存操作

  2. C语言实现,优化过的数据结构,基于几种基础的数据结构,redis做了大量的优化,性能极高

  3. 使用单线程,无上下文的切换文本

  4. 基于非阻塞IO的多路复用机制

redis的RDB是什么

Redis持久化方案分为AOFRDB两种

ROB持久化可以分为手动执行也可以根据配置定期执行,它的作用是将某个时间节点上的数据状态保存到RDB文件中,RDB文件是一个压缩的二进制文件,通过它可以还原某个时刻数据库的状态.由于RDB文件是保存在硬盘上的,所有即使redis崩溃或者退出,只要RDB存在,就可以用它还恢复还原数据库的状态

通过save或者BGSAVE 来生成RDB文件

Save命令会阻塞redis进程,直到RDB文件生成完毕,在进程阻塞期间,redis不能处理任何命令请求,这显然是不合适的;

BGSAVE则是会fork出一个子进程,然后由子进程去负责生成RDB文件,父进程还可以继续处理命令请求,不会阻塞进程;

redis的AOF持久化

AOF是通过命令去进行持久化,虽然数据不会丢失,但是redis宕机后数据恢复很慢,所以我们一般不使用AOF;

redis雪崩

存在redis中的数据在同一时间过期,导致所有的请求全部打在了数据库上,数据库承受不住压力而宕机;

解决方案:

  1. 设置不同的过期时间

  2. 对请求进行一个限流,防止太多的请求打在数据库上,导致数据库宕机

Redis如何保证原子性

通过lua脚本去保证操作的原子性

redis内存容量增加后,会带来什么问题

1. 内存快照RDB生成和恢复的效率低

2. 主从节点全量同步时间增长,缓冲区溢出

如何保证缓存和数据库的一致性

1. 缓存只读,缓存只提供数据的读取

2. 当数据库的数据修改时,实时同步到数据库中(先更新数据库,再更新缓存)

redis的删除策略

  1. 惰性删除:当获取这个key的时候,判断它是否过期,如果过期,则删除

    优点:不会删除其他键,不会花费cpu在那些无关的key上

    缺点:有些key不会去访问,就不会删除,造成数据积压

  2. 定期删除:每100ms随机抽取一些设置了过期时间的key,检查是否过期,过期就删除

    优点:不会造成内存积压,限制操作时长和频率来减少对CPU时间的影响

    缺点:定时操作太频繁浪费CPU性能,不频繁又可能造成数据积压

111.JVM

jvm分区

Heap (堆区):主要存储new出来的对象实例,Java堆中细分为:新生代和老年代,一个新生代分为1个Eden区和2个Survivor区,说明:绝大部分对象在Eden区生成,当Eden区装填满的时候,会触发Young Garbage Collection,即YGC。垃圾回收的时候,在Eden区实现清除策略,没有被引用的对象则直接回收。依然存活的对象会被移送到Survivor区。Survivor区分为s0和s1两块内存空间。每次YGC的时候,它们将存活的对象复制到未使用的那块空间,然后将当前正在使用的空间完全清除,交换两块空间的使用状态。如果YGC要移送的对象大于Survivor区容量的上限,则直接移交给老年代

元空间区:jdk1.7的方法区移到了元空间,比如类元信息、字段、静态属性、方法、常量等都移动到元空间区,元空间并不在虚拟机中,而是使用本地内存

栈:栈里面存的都是一些局部变量,比如8大基本数量类型,还有线程运行,方法运行都在栈里面,另外创建对象的时候的引用也是存在栈里面的

程序计数器:是一块较小的内存空间。是线程私有的。它可以看作是当前线程所执行的字节码的行号指示器器

112.类加载

类加载器有这几个:

启动类加载器:jvm启动的时候,会优先加载<JAVA_HOME>\lib这个目录的核心类库。

扩展类加载器:负责加载<JAVA_HOME>\lib\ext这个目录的类。

应用程序类加载器:负责加载我们写的代码。

自定义类加载器:根据我们的需要,加载特定的类。

下图展示了类加载器直接的层次关系,成为类加载器的双亲委派模型。双亲委派模型要求除了顶层的启动类加载器外,其余的类加载器都应当有自己的父类加载器。

它的工作过程是这样的:

  1. 应用程序类加载器收到了类的加载请求,先问扩展类加载器是否可以加载。
  2. 扩展类加载器也不会直接去加载,他也是向上级启动类加载器询问是否可以加载。
  3. 启动类加载器在自己负责的目录搜索了一下,发现自己找不到这个类,就说不行,你自己加载吧。
  4. 扩展类加载器在自己负责的目录搜索了一下,发现自己找不到这个类,就说不行,你自己加载吧。
  5. 应用程序类加载器在自己负责的目录搜索了一下,找到了这个类,把Hello类加载进来。

双亲委派模型一个显而易见的好处就是Java类随着它的类加载器一起具备了一种带有优先级的层次关系。

113.双亲委派存在得意义是什么?

保证系统的安全

114.垃圾回收器有哪些?

串行垃圾回收器(Serial)C尔瑞奥:它为单线程环境设计并且只使用一个线程进行垃圾回收,会暂停所有的用户线程。所以不适合服务器环境。 并行垃圾回收器(Parallel)啪瑞捞:多个垃圾回收线程并行工作,此时用户线程是暂停的,适用于科学计算/大数据处理等弱交互场景。jdk8默认的是使用的Parallel并行回收器 并发垃圾回收器(CMS):用户线程和垃圾收集线程同时执行(不一定是并行,可能交替执行),不需要停顿用户线程。互联网公司多用它,适用于对响应时间有要求的场景。

115.垃圾回收算法?

标记清除:

复制算法:

标记整理:

分代收集算法:

116.JVM调优的几种场景

CPU占用过高

CPU过高的原因一般是,死循环,递归,计算量大,线程数过多,怎么确定CPU飙升的问题如下:

用top命令查看cpu占用情况

用top -Hp命令查看线程的情况

可以看到是线程id为7287这个线程一直在占用cpu,把线程号转换为16进制,用jstack工具查看线程栈情况

117.内存溢出解决

程序发生内存泄漏后,进程的可用内存会慢慢变少,最后的结果就是抛出OOM错误。发生OOM错误后可能会想到是内存不够大,于是把-Xmx参数调大,然后重启应用。这么做的结果就是,过了一段时间后,OOM依然会出现。最后无法再调大最大堆内存了,结果就是只能每隔一段时间重启一下应用

118.用jstat分析gc活动情况

jstat是一个统计java进程内存使用情况和gc活动的工具,参数可以有很多,可以通过jstat -help查看所有参数以及含义

119.用jmap工具dump出内存快照

链接:mp.weixin.qq.com/s?__biz=Mzk…

120.雪花算法

使用64位long类型的数字作为全局唯一id,并且id有时间戳的引入,保持了自增 第一个部分,是 1 个 bit:0,这个是无意义的。 第二个部分是 41 个 bit:表示的是时间戳。 第三个部分是 5 个 bit:表示的是机房 id,10001。 第四个部分是 5 个 bit:表示的是机器 id,1 1001。 第五个部分是 12 个 bit:表示的序号,就是某个机房某台机器上这一毫秒内同时生成的 id 的序号,0000 00000000

121.雪花算法的优点

id自增:索引效率高 高性能 高可用:在内存中生成,不依赖于数据库 容量大:可以在每秒中生成数百万个自增的id8

122.雪花算法缺点

它在生成的时候引入了时间戳,如果系统时间被回调,或者更改,会导致id重复;而解决这个问题的方法是可以进行算法的更改,抽出将10bit的机器去id优化,改成与业务表相关的

123.使用自增id的缺点

不具有连续性,如果说中途删除了最大值,那么下一个生成的时候就会出现跳号(1,2,3最大是3 那么吧3删除了,下一次就会出现4,那么此时数据库中就有1,2,4) 如果在进行合表的情况下,历史表的主键id会和现在的数据表有一个id的重复

124.单点登录

单点登录:(核心是sso中的cookie是否有值)

用户去访问应用1,去做登录,如果sso中没有cookie的值就去重定向到login页面去做登录,登录完成之后sso中就有了cookie的值,并且在登录成功后生成service Ticket,去判断service Ticket的值,如果相同就登录成功,那么自己也有了cookie值,会将生成的ticket值存入Redis中去

用户去访问应用2,如果sso中cookie中有值那么直接去判断ticket是否和存入redis中的一样,如果一样就成功了,同时自己的cookie中也有值了如果不一致就重定向到登录页面做登录操作。

125.场景问题:生产环境上接口响应比较慢,怎么排查?

首先判断偶尔慢还是经常慢

偶尔慢:可能涉及到网络问题

经常慢:先试一下本地的响应速度,如果本地响应慢,则建索引异步编排来解决。确实是生产环境慢的话,用阿尔萨斯中的命令可以看到类或方法的调用时长,来针对性的进行解决

126.场景问题:吃内存比较厉害,但是还没有内存溢出,怎么解决?

JDK自带工具jstat看GC的回收频率,查看minor gcfull gc回收频率。

127.空间局部性原理怎么理解?

如果某一个位置的数据被访问,那么这个位置附近的数据很可能被访问。

举个简单的例子:B+树 因为B+树的叶子结点都是顺序排列并且相连,如果CPU进行读取操作的时候,会一次性的读取4个字节作为一个缓存行,这样会把该数据的兄弟结点一起读取到缓存中。缓存的读取速度是非常的,如果我们再一次用到类似的数据,会发现早已经存入缓存中了。这样就充分利用了空间局部性原理

128.如何防止重复下单?

用户每次在前台点击下单按钮的时候,都会传到后台一个唯一的订单号,可以再数据库层面把该字段设置为唯一(幂等操作)。这样如果有重复订单号传输进来,就会抛出异常,友好的提示用户:请勿重复下单

129.延迟队列消息时间结束时,用户支付失败,但库存预减了,解决方案

对接第三方支付平台,查询该订单是否支付。根据返回的支付结果进行业务逻辑的操作。如果用户已支付,那可能是因为网络的问题导致订单的状态修改失败,我们重新修改即可。如果用户确实没有支付成功,则取消订单,库存回滚。

130.Springclode和SringAlibaba的区别是什么?

名称SpringCloudSpring Alibaba
注册中心Eureka、ConsulNacos
配置中心SpringCloud ConfigNacos
网 关SpringCloud ZuulSpringCloud Gateway
负载均衡RibbonLoadbalancer
熔断降级HystrixSentinel
服务调用FeignOpenFeign

131.说说什么是MVCC?

多版本并发控制( MVCC=Multi-Version Concurrency Control ),是一种用来解决读 - 写冲突的无锁并发控制。也就是为事务分配单向增长的时间戳,为每个修改保存一个版本。版本与事务时间戳 关联,读操作只读该事务开始前的数据库的快照(复制了一份数据)。这样在读操作不用阻塞写操作,写操作不用阻塞读操作的同时,避免了脏读和不可重复读。

132.MVCC可以为数据库解决什么问题?

在并发读写数据库时,可以做到在读操作时不用阻塞写操作,写操作也不用阻塞读操作,提高了数据库并发读写的性能。同时还可以解决脏读、幻读、不可重复读等事务隔离问题,但不能解决更新丢失问题。

133.说说MVCC的实现原理

MVCC的目的就是多版本并发控制,在数据库中的实现,就是为了解决读写冲突 ,它的实现原理主要是依赖记录中的3个隐式字段、undo 日志、Read View 来实现的。

134.说⼀下HashMap的Put⽅法

⼤体流程:

1. 根据Key通过哈希算法与与运算得出数组下标

2. 如果数组下标位置元素为空,则将key和value封装为Entry对象(JDK1.7中是Entry对象,JDK1.8中是Node对象)并放⼊该位置

3. 如果数组下标位置元素不为空,则要分情况讨论

  • a. 如果是JDK1.7,则先判断是否需要扩容,如果要扩容就进⾏扩容,如果不⽤扩容就⽣成Entry对象,并使⽤头插法添加到当前位置的链表中

  • b. 如果是JDK1.8,则会先判断当前位置上的Node的类型,看是红⿊树Node,还是链表Node

    i. 如果是红⿊树Node,则将key和value封装为⼀个红⿊树节点并添加到红⿊树中去,在这个过程中会判断红⿊树中是否存在当前key,如果存在则更新value

    ii. 如果此位置上的Node对象是链表节点,则将key和value封装为⼀个链表Node并通过尾插法插⼊到链表的最后位置去,因为是尾插法,所以需要遍历链表,在遍历链表的过程中会判断是否存在当前key,如果存在则更新value,当遍历完链表后,将新链表Node插⼊到链表中,插⼊到链表后,会看当前链表的节点个数,如果⼤于等于8,那么则会将该链表转成红⿊树

    iii. 将key和value封装为Node插⼊到链表或红⿊树中后,再判断是否需要进⾏扩容,如果需要就扩容,如果不需要就结束PUT⽅法

135.场景问题:MQ数据量大的时候,消息挤压的情况怎么处理?

分析:消费能力不足

  1. 加机器,提高消费能力

  2. 看一下代码,是否是代码拖累,可以采用多线程异步之类提高效率

  3. 采用批量添加,批量消费。多线程进行处理

136. 场景问题:如果项目上线了,出现问题你是怎么看日志的?

ELK看日志。把日志保存到ES中,从页面看日志 ,有一个可视化页面

docker看日志,有弊端:可以进行删除杀死等违规操作

137.为什么使用xxl-job?

  • 轻量级,开箱即用,操作简易,上手快,与Spring有非常好的集成。
  • 使用XXL-JOB配置来避免重复调度
  • 使用定时任务场景比较多的情况下,使用非常便捷

138.你们项目是怎么发布的?

自动化发布,docker

为什么到8以后使用红黑树

log4=2 log8=3 当前链表长度除以二 根据查询的速率以及空间的利用

负载因子为啥大了,空间利用率.... 扩容机制,扩容*负载因子

HashMap初始容量,16,HashTable 初始容量 11

sql优化,看type字段走的时候什么索引,key看的是索引字段

线程池底层原理,中的核心线程数使用完后会销毁么?

核心线程通常不会回收,java核心线程池的回收由allowCoreThreadTimeOut参数控制,默认为false,若开启为true,则此时线程池中不论核心线程还是非核心线程,只要其空闲时间达到keepAliveTime都会被回收。但如果这样就违背了线程池的初衷(减少线程创建和开销),所以默认该参数为false。