本文已参与「新人创作礼」活动,一起开启掘金创作之路。
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.单例模式理解?
懒汉模式使用的是双重效验锁和 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 ,采用三级缓存
提前暴露的对象,虽然已经实例化,但是还没有完成实例化,是一个不完整的对象,这个对象存放在二级缓存中,对于三级缓存十分重要
- 调用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 有什么作用?
- 防止指令重排序,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中去查询丢失的数据
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 分布式集群解决方案 )
-
数据按照 slot 存储分布在多个节点,节点间数据共享,可动态调整数据分布。
-
可扩展性:可线性扩展到 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号哈希槽
- 每个节点会保存一份数据分布表,节点会将自己的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,而是引入了哈希槽的概念
一般在实际生产中,服务不会部署成单节点,主要是有三个原因.
- 容易出现单点故障,导致服务不可用
- 单节点处理所有的请求,吞吐量有限
- 单节点容量有限
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跳跃表实现
- 跳跃表是有序集合zset的底层实现之一。
- 跳跃表就是在链表的基础上,增加多级索引提升查找效率。
redis为什么快
-
完全基于内存操作
-
C语言实现,优化过的数据结构,基于几种基础的数据结构,redis做了大量的优化,性能极高
-
使用单线程,无上下文的切换文本
-
基于非阻塞IO的多路复用机制
redis的RDB是什么
Redis持久化方案分为AOF和RDB两种
ROB持久化可以分为手动执行也可以根据配置定期执行,它的作用是将某个时间节点上的数据状态保存到RDB文件中,RDB文件是一个压缩的二进制文件,通过它可以还原某个时刻数据库的状态.由于RDB文件是保存在硬盘上的,所有即使redis崩溃或者退出,只要RDB存在,就可以用它还恢复还原数据库的状态
通过save或者BGSAVE 来生成RDB文件
Save命令会阻塞redis进程,直到RDB文件生成完毕,在进程阻塞期间,redis不能处理任何命令请求,这显然是不合适的;
BGSAVE则是会fork出一个子进程,然后由子进程去负责生成RDB文件,父进程还可以继续处理命令请求,不会阻塞进程;
redis的AOF持久化
AOF是通过命令去进行持久化,虽然数据不会丢失,但是redis宕机后数据恢复很慢,所以我们一般不使用AOF;
redis雪崩
存在redis中的数据在同一时间过期,导致所有的请求全部打在了数据库上,数据库承受不住压力而宕机;
解决方案:
-
设置不同的过期时间
-
对请求进行一个限流,防止太多的请求打在数据库上,导致数据库宕机
Redis如何保证原子性
通过lua脚本去保证操作的原子性
redis内存容量增加后,会带来什么问题
1. 内存快照RDB生成和恢复的效率低
2. 主从节点全量同步时间增长,缓冲区溢出
如何保证缓存和数据库的一致性
1. 缓存只读,缓存只提供数据的读取
2. 当数据库的数据修改时,实时同步到数据库中(先更新数据库,再更新缓存)
redis的删除策略
-
惰性删除:当获取这个key的时候,判断它是否过期,如果过期,则删除
优点:不会删除其他键,不会花费cpu在那些无关的key上
缺点:有些key不会去访问,就不会删除,造成数据积压
-
定期删除:每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这个目录的类。
应用程序类加载器:负责加载我们写的代码。
自定义类加载器:根据我们的需要,加载特定的类。
下图展示了类加载器直接的层次关系,成为类加载器的双亲委派模型。双亲委派模型要求除了顶层的启动类加载器外,其余的类加载器都应当有自己的父类加载器。
它的工作过程是这样的:
- 应用程序类加载器收到了类的加载请求,先问扩展类加载器是否可以加载。
- 扩展类加载器也不会直接去加载,他也是向上级启动类加载器询问是否可以加载。
- 启动类加载器在自己负责的目录搜索了一下,发现自己找不到这个类,就说不行,你自己加载吧。
- 扩展类加载器在自己负责的目录搜索了一下,发现自己找不到这个类,就说不行,你自己加载吧。
- 应用程序类加载器在自己负责的目录搜索了一下,找到了这个类,把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 gc和full gc回收频率。
127.空间局部性原理怎么理解?
如果某一个位置的数据被访问,那么这个位置附近的数据很可能被访问。
举个简单的例子:B+树 因为B+树的叶子结点都是顺序排列并且相连,如果CPU进行读取操作的时候,会一次性的读取4个字节作为一个缓存行,这样会把该数据的兄弟结点一起读取到缓存中。缓存的读取速度是非常快的,如果我们再一次用到类似的数据,会发现早已经存入缓存中了。这样就充分利用了空间局部性原理。
128.如何防止重复下单?
用户每次在前台点击下单按钮的时候,都会传到后台一个唯一的订单号,可以再数据库层面把该字段设置为唯一(幂等操作)。这样如果有重复订单号传输进来,就会抛出异常,友好的提示用户:请勿重复下单
129.延迟队列消息时间结束时,用户支付失败,但库存预减了,解决方案
对接第三方支付平台,查询该订单是否支付。根据返回的支付结果进行业务逻辑的操作。如果用户已支付,那可能是因为网络的问题导致订单的状态修改失败,我们重新修改即可。如果用户确实没有支付成功,则取消订单,库存回滚。
130.Springclode和SringAlibaba的区别是什么?
| 名称 | SpringCloud | Spring Alibaba |
|---|---|---|
| 注册中心 | Eureka、Consul | Nacos |
| 配置中心 | SpringCloud Config | Nacos |
| 网 关 | SpringCloud Zuul | SpringCloud Gateway |
| 负载均衡 | Ribbon | Loadbalancer |
| 熔断降级 | Hystrix | Sentinel |
| 服务调用 | Feign | OpenFeign |
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数据量大的时候,消息挤压的情况怎么处理?
分析:消费能力不足
-
加机器,提高消费能力
-
看一下代码,是否是代码拖累,可以采用多线程,异步之类提高效率
-
采用批量添加,批量消费。多线程进行处理
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。