01开篇词
## 一、什么是高效的面试?
自信。
二、要怎么准备面试?
第一 拉伸知识的广度; 了解互联网的主要技术栈,形成知识网络。
第二 提升知识的深度: 由浅到深,由点到面,对每个技术栈形成梯度,整理梳理。每个梯度都要准备一到两个有代表性的高频面试题。种子题目。
三、本专题的特点
广、全、精、散、深、种子
02JAVA并发篇
## 一、JAVA如何开启线程?怎么保证线程安全?
线程和进程的区别:进程是 *** 作系统进行资源分配的最小单元。线程是 *** 作系统进行任务分配的最小单元,线程隶属于进程。
如何开启线程? 1、继承Thread类,重写run方法。 2、实现Runnable接口,实现run方法。3、实现Callable接口,实现call方法。通过FutureTask创建一个线程,获取到线程执行的返回值。4、通过线程池来开启线程。
怎么保证线程安全? 加锁: 1、 JVM提供的锁, 也就是Synchronized关键字。 2、 JDK提供的各种锁 Lock。
二、 Volatile和Synchronized有什么区别?Volatile能不能保证线程安全?DCL(Double Check Lock)单例为什么要加Volatile?
1、Synchronized关键字,用来加锁。 Volatile只是保持变量的线程可见性。通常适用于一个线程写,多个线程读的场景。
2、不能。Volatile关键字只能保证线程可见性, 不能保证原子性。
3、Volatile防止指令重排。在DCL中,防止高并发情况下,指令重排造成的线程安全问题。
三、JAVA线程锁机制是怎样的?偏向锁、轻量级锁、重量级锁有什么区别?锁机制是如何升级的?
1、JAVA的锁就是在对象的Markword中记录一个锁状态。无锁,偏向锁,轻量级锁,重量级锁对应不同的锁状态。
2、JAVA的锁机制就是根据资源竞争的激烈程度不断进行锁升级的过程。
3、
四、谈谈你对AQS的理解。AQS如何实现可重入锁?
1、AQS是一个JAVA线程同步的框架。是JDK中很多锁工具的核心实现框架。
2、 在AQS中,维护了一个信号量state和一个线程组成的双向链表队列。其中,这个线程队列,就是用来给线程排队的,而state就像是一个红绿灯,用来控制线程排队或者放行的。 在不同的场景下,有不用的意义。
3、在可重入锁这个场景下,state就用来表示加锁的次数。0标识无锁,每加一次锁,state就加1。释放锁state就减1。
五、有A,B,C三个线程,如何保证三个线程同时执行?如何在并发情况下保证三个线程依次执行?如何保证三个线程有序交错进行?
CountDownLatch, CylicBarrier, Semaphore。
六、如何对一个字符串快速进行排序?
Fork/Join框架
03JAVA网络通信篇
## 一、TCP和UDP有什么区别?TCP为什么是三次握手,而不是两次?
TCP Transfer Control Protocol 是一种面向连接的、可靠的、传输层通信协议。
特点: 好比是打电话:面向连接的,点对点的通信,高可靠的,效率比较低,占用的系统资源比较多。
UDP User Datagram Protocol 是一种无连接的, 不可靠的、传输层通信协议。
特点:好比是广播:不需要连接,发送方不管接收方有没有准备好,直接发消息;可以进行广播发送的;传输不可靠,有可能丢失消息;效率比较高;协议就会比较简单,占用的系统资源就比较少。
TCP建立连接三次握手,断开连接四次挥手。
3、如果是两次握手,可能造成连接资源浪费的情况。
二、JAVA有哪几种IO模型?有什么区别?
BIO 同步阻塞IO。 可靠性差,吞吐量低,适用于连接比较少且比较固定的场景。JDK1.4之前唯一的选择。编程模型最简单
NIO 同步非阻塞IO 可靠性比较好,吞吐量也比较高,适用于连接比较多并且连接比较短(轻 *** 作),例如聊天室。JDK1.4开始支持。编程模型最复杂。
AIO 异步非阻塞IO 可靠性是最好的,吞吐量也是非常高。适用于连接比较多,并且连接比较长(重 *** 作)。例如 相册服务器。JDK7版本才支持的。编程模型比较简单,需要 *** 作系统来支持。
同步 、异步- 针对请求 和 阻塞、非阻塞 – 针对客户端。
在一个网络请求中,客户端会发一个请求到服务端。
1、客户端发了请求后,就一直等着服务端响应。客户端:阻塞。 请求:同步
2、客户端发了请求后,就去干别的事情了。时不时的过来检查服务端是否给出了相应。 客户端:非阻塞。 请求:同步。
3、换成异步请求。客户端发了请求后,就坐在椅子上,等着服务端返回响应。 客户端:阻塞。 请求:异步
4、客户端发了请求后,就去干别的事情了。等到服务端给出响应后,再过来处理业务逻辑。 客户端;非阻塞。 请求:异步。
三、JAVA NIO的几个核心组件是什么?分别有什么作用?
Channel Buffer Selector
channel类似于一流。 每个channel对应一个buffer缓冲区。channel会注册到selector。
select会根据channel上发生的读写事件,将请求交由某个空闲的线程处理。selector对应一个或者多个线程。
Buffer和Channel都是可读可写的。
四、select,poll和epoll有什么区别?
他们是NIO中多路复用的三种实现机制,是由Linux *** 作系统提供的。
用户空间和内核空间: *** 作系统为了保护系统安全,将内核划分为两个部分,一个是用户空间,一个是内核空间。用户空间不能直接访问底层的硬件设备,必须通过内核空间。
文件描述符 File Descriptor(FD):是一个抽象的概念,形式上是一个整数,实际上是一个索引值。指向内核中为每个进程维护进程所打开的文件的记录表。当程序打开一个文件或者创建一个文件时, 内核就会向进程返回一个FD。Unix,Linux
select机制: 会维护一个FD的结合 fd_set。将fd_set从用户空间复制到内核空间,激活socket。 x64 2048 fd_set是一个数组结构
Poll机制: 和selecter机制是差不多的,把fd_set结构进行了优化,FD集合的大小就突破了 *** 作系统的限制。 pollfd结构来代替fd_set,通过链表实现的。
EPoll:Event Poll.Epoll不再扫描所有的FD,只将用户关心的FD的事件存放到内核的一个事件表当中。这样,可以减少用户空间与内核空间之前需要拷贝的数据。
简单总结:
*** 作方式 底层实现 最大连接数 IO效率
select: 遍历 数组 受限于内核 一般
poll 遍历 链表 无上限 一般
epoll 事件回调 红黑树 无上限 高
java的NIO当中是用的那种机制? 可以查看DefaultSelectorProvider源码。在windows下,WindowsSelectorProvider。而Linux下,根据Linux的内核版本,2.6版本以上,就是EPollSelectorProvider, 否则就是默认的PollSelectorProvider.
select 1984年出现, poll 1997年出现, EPoll 2002年
五、描述下HTTP和HTTPS的区别。
HTTP: 是互联网上应用最为广泛的一种网络通信协议,基于TCP,可以使浏览器工作更为高效,减少网络传输。
HTTPS: 是HTTP的加强版,可以认为是HTTP+SSL(Secure Socket Layer)。在HTTP的基础上增加了一系列的安全机制。一方面保证数据传输安全,另一位方面对访问者增加了验证机制。是目前现行架构下,最为安全的解决方案。
主要区别:
1、HTTP的连接是简单无状态的,HTTPS的数据传输是经过证书加密的,安全性更高。
2、HTTP是免费的, 而HTTPS需要申请证书,而证书通常是需要收费的,并且费用一般不低。
3、他们的传输协议不通过,所以他们使用的端口也是不一样的, HTTP默认是80端口,而HTTPS默认是443端口。
HTTPS的缺点:
1、HTTPS的握手协议比较费时,所以会影响服务的响应速度以及吞吐量。
2、HTTPS也并不是完全安全的。他的证书体系其实并不是完全安全的。并且HTTPS在面对DDOS这样的攻击时,几乎起不到任何作用。
3、证书需要费钱,并且功能越强大的证书费用越高。
04JVM调优篇
## 一、说一说JVM的内存模型。
二、JAVA类加载的全过程是怎样的?什么是双亲委派机制?有什么作用?
JAVA的类加载器: AppClassloader -> ExtClassloader -> BootStrap Classloader
每种类加载器都有他自己的加载目录。
JAVA中的类加载器: AppClassLoader , ExtClassLoader -> URLClassLoader ->SecureClassLoader -> ClassLoader
每个类加载器对他加载过的类,都是有一个缓存的。
双亲委派:向上委托查找,向下委托加载。 作用:保护JAVA的层的类不会被应用程序覆盖。
类加载过程: 加载 -》 连接 -》 初始化
加载:把Java的字节码数据加载到JVM内存当中,并映射成JVM认可的数据结构。
连接:分为三个小的阶段: 1、验证:检查加载到的字节信息是否符合JVM规范。
2、准备: 创建类或接口的静态变量,并赋初始值 半初始化状态
3、解析:把符号引用转为直接引用
初始化:
一个对象从加载到JVM,再到被GC清除,都经历了什么过程?
method{ ClassLoaderDemo1 c =new ClassLoaderDemo1(); c.xxx} GC
1、用户创建一个对象,JVM首先需要到方法区去找对象的类型信息。然后再创建对象。
2、JVM要实例化一个对象,首先要在堆当中先创建一个对象。-> 半初始化状态
3、对象首先会分配在堆内存中新生代的Eden。然后经过一次Minor GC,对象如果存活,就会进入S区。在后续的每次GC中,如果对象一直存活,就会在S区来回拷贝,每移动一次,年龄加1。-> 多大年龄才会移入老年代? 年龄最大15, 超过一定年龄后,对象转入老年代。
4、当方法执行结束后,栈中的指针会先移除掉。
5、堆中的对象,经过Full GC,就会被标记为垃圾,然后被GC线程清理掉。
三、怎么确定一个对象到底是不是垃圾? 什么是GC Root?
有两种定位垃圾的方式:
1、引用计数: 这种方式是给堆内存当中的每个对象记录一个引用个数。引用个数为0的就认为是垃圾。这是早期JDK中使用的方式。引用计数无法解决循环引用的问题。
2、根可达算法: 这种方式是在内存中,从引用根对象向下一直找引用,找不到的对象就是垃圾。
哪些是GC Root? Stack -> JVM Stack, Native Stack, class类, run-time constant pool 常量池, static reference 静态变量。
四、JVM有哪些垃圾回收算法?
MarkSweep 标记清除算法
这个算法分为两个阶段,标记阶段:把垃圾内存标记出来,清除阶段:直接将垃圾内存回收。
这种算法是比较简单的,但是有个很严重的问题,就是会产生大量的内存碎片。
Copying 拷贝算法
为了解决标记清除算法的内存碎片问题,就产生了拷贝算法。拷贝算法将内存分为大小相等的两半,每次只使用其中一半。垃圾回收时,将当前这一块的存活对象全部拷贝到另一半,然后当前这一半内存就可以直接清除。
这种算法没有内存碎片,但是他的问题就在于浪费空间。而且,他的效率跟存货对象的个数有关。
MarkCompack 标记压缩算法
为了解决拷贝算法的缺陷,就提出了标记压缩算法。这种算法在标记阶段跟标记清除算法是一样的,但是在完成标记之后,不是直接清理垃圾内存,而是将存活对象往一端移动,然后将端边界以外的所有内存直接清除。
这三种算法各有利弊,各自有各自的适合场景。
五、JVM有哪些垃圾回收器?他们都是怎么工作的?什么是STW?他都发生在哪些阶段?什么是三色标记?如何解决错标记和漏标记的问题?为什么要设计这么多的垃圾回收器?
STW: Stop-The-World。是在垃圾回收算法执行过程当中,需要将JVM内存冻结的一种状态。在STW状态下,JAVA的所有线程都是停止执行的-GC线程除外,native方法可以执行,但是,不能与JVM交互。GC各种算法优化的重点,就是减少STW,同时这也是JVM调优的重点。
JVM的垃圾回收器:
Serial 串行
整体过程比较简单,就像踢足球一样,需要GC时,直接暂停,GC完了再继续。
这个垃圾回收器,是早期垃圾回收器,只有一个线程执行GC。在多CPU架构下,性能就会下降严重。只适用于几十兆的内存空间。
Parallel 并行
在串行基础上,增加多线程GC。PS+PO这种组合是JDK1.8默认的垃圾回收器。在多CPU的架构下,性能会比Serial高很多。
CMS Concurrent Mark Sweep
核心思想,就是将STW打散,让一部分GC线程与用户线程并发执行。 整个GC过程分为四个阶段
1、初始标记阶段:STW 只标记出根对象直接引用的对象。
2、并发标记:继续标记其他对象,与应用程序是并发执行。
3、重新标记: STW 对并发执行阶段的对象进行重新标记。
4、并发清除:并行。将产生的垃圾清除。清除过程中,应用程序又会不断的产生新的垃圾,叫做浮动垃圾。这些垃圾就要留到下一次GC过程中清除。
G1 Garbage First 垃圾优先
他的内存模型是实际不分代,但是逻辑上是分代的。在内存模型中,对于堆内存就不再分老年代和新生代,而是划分成一个一个的小内存块,叫做Region。每个Region可以隶属于不同的年代。
GC分为四个阶段:
第一:初始标记 标记出GCRoot直接引用的对象。STW
第二:标记Region,通过RSet标记出上一个阶段标记的Region引用到的Old区Region。
第三:并发标记阶段:跟CMS的步骤是差不多的。只是遍历的范围不再是整个Old区,而只需要遍历第二步标记出来的Region。
第四:重新标记: 跟CMS中的重新标记过程是差不多的。
第五:垃圾清理:与CMS不同的是,G1可以采用拷贝算法,直接将整个Region中的对象拷贝到另一个Region。而这个阶段,G1只选择垃圾较多的Region来清理,并不是完全清理。
CMS的核心算法就是三色标记。
三色标记:是一种逻辑上的抽象。将每个内存对象分成三种颜色: 黑色:表示自己和成员变量都已经标记完毕。 灰色:自己标记完了,但是成员变量还没有完全标记完。白色:自己未标记完。
CMS通过增量标记 increment update 的方式来解决漏标的问题。
六、如何进行JVM调优?JVM参数有哪些?怎么查看一个JAVA进程的JVM参数?谈谈你了解的JVM参数。如果一个java程序每次运行一段时间后,就变得非常卡顿,你准备如何对他进行优化?
JVM调优主要就是通过定制JVM运行参数来提高JAVA应用程度的运行数据
JVM参数大致可以分为三类:
1、 标注指令: -开头,这些是所有的HotSpot都支持的参数。可以用java -help 打印出来。
2、非标准指令: -X开头,这些指令通常是跟特定的HotSpot版本对应的。可以用java -X 打印出来。
3、不稳定参数: -XX 开头,这一类参数是跟特定HotSpot版本对应的,并且变化非常大。详细的文档资料非常少。在JDK1.8版本下,有几个常用的不稳定指令:
java -XX:+PrintCommandLineFlags : 查看当前命令的不稳定指令。
java -XX:+PrintFlagsInitial : 查看所有不稳定指令的默认值。
java -XX:+PrintFlagsFinal: 查看所有不稳定指令最终生效的实际值。
05消息队列篇
## 一、MQ有什么用?有哪些具体的使用场景?
MQ: MessageQueue,消息队列。 队列是一种FIFO先进先出的数据结构。消息由生产者发送到MQ进行排队,然后由消费者对消息进行处理。QQ、 微信 就是典型的MQ场景。
MQ的作用主要有三个方面:
1、异步:
例子:快递。 快递员-> 菜鸟驿站<- 客户
作用:异步能提高系统的响应速度和吞吐量。
2、解耦:
例子:《Thinking in java》 -> 编辑社
作用:服务之间进行解耦,可以减少服务之间的影响,提高系统的稳定性和可扩展性。
另外,解耦之后可以实现数据分发。生产者发送一个消息后,可以由多个消费者来处理。
3、削峰:
例子:长江涨水->三峡大坝
作用:以稳定的系统资源应对突发的流量冲击。
MQ的缺点:
1、系统可用性降低: 一旦MQ宕机,整个业务就会产生影响。高可用
2、系统的复杂度提高: 引入MQ之后,数据链路就会变得很复杂。如何保证消息不丢失?消息不会重复调用?怎么保证消息的顺序性?、、、、、
3、数据一致性: A系统发消息,需要由B、C两个系统一同处理。如果B系统处理成功、C系统处理失败,这就会造成数据一致性的问题。
二、如何进行产品选型?
Kafka
优点: 吞吐量非常大,性能非常好,集群高可用。
缺点:会丢数据,功能比较单一。
使用场景:日志分析、大数据采集
RabbitMQ
优点: 消息可靠性高,功能全面。
缺点:吞吐量比较低,消息积累会严重影响性能。erlang语言不好定制。
使用场景:小规模场景。
RocketMQ
优点:高吞吐、高性能、高可用,功能非常全面。
缺点:开源版功能不如云上商业版。官方文档和周边生态还不够成熟。客户端只支持java。
使用场景:几乎是全场景。
三、如何保证消息不丢失?
1、哪些环节会造成消息丢失?
2、怎么去防止消息丢失。
2.1 生产者发送消息不丢失
kafka: 消息发送+回调
RocketMQ: 1、消息发送+回调。2、事务消息。
RabbitMQ: 1、消息发送+回调
2、 手动事务: channel.txSelect()开启事务, channel.txCommit()提交事务, channel.txRollback()回滚事务。这种方式对channel是会产生阻塞的,造成吞吐量下降。
3、Publisher Confirm。整个处理流程跟RocketMQ的事务消息,基本是一样的。
2.2 MQ主从消息同步不丢失
RocketMQ: 1、普通集群中,同步同步、异步同步。异步同步效率更高,但是有丢消息的风险。同步同步就不会丢消息。
2、Dledger集群-两阶段提交:
RabbitMQ: 普通集群:消息是分散存储的,节点之间不会主动进行消息同步,是有可能丢失消息的。
镜像集群:镜像集群会在节点之间主动进行数据同步,这样数据安全性得到提高。
Kafka: 通常都是用在允许消息少量丢失的场景。acks。0,1,all
2.3 MQ消息存盘不丢失
RocketMQ: 同步刷盘 异步刷盘:异步刷盘效率更高,但是有可能丢消息。同步刷盘消息安全性更高,但是效率会降低。
RabbitMQ: 将队列配置成持久化队列。新增的Quorum类型的队列,会采用Raft协议来进行消息同步。
2.4 MQ消费者消费消息不丢失
RocketMQ: 使用默认的方式消费就行, 不要采用异步方式。
RabbitMQ: autoCommit -> 手动提交offset
Kafka: 手动提交offset
四、如何保证消息消费的幂等性?
其实就是要方式消费者重复消费消息的问题。
所有MQ产品并没有提供主动解决幂等性的机制,需要由消费者自行控制。
RocketMQ: 给每个消息分配了个MessageID。这个MessageID就可以作为消费者判断幂等的依据。这种方式不太建议。
最好的方式就是自己带一个有业务标识的ID,来进行幂等判断。OrderID
统一ID分配。
五、如何保证消息的顺序?
全局有序和局部有序: MQ只需要保证局部有序,不需要保证全局有序。
生产者把一组有序的消息放到同一个队列当中,而消费者一次消费整个队列当中的消息。
RocketMQ中有完整的设计,但是在RabbitMQ和Kafka当中,并没有完整的设计,需要自己进行设计。
RabbitMQ:要保证目标exchange只对应一个队列。并且一个队列只对应一个消费者。
Kafka: 生产者通过定制partition分配规则,将消息分配到同一个partition。 Topic下只对应一个消费者。
六、如何保证消息的高效读写?
零拷贝: kafka和RocketMQ都是通过零拷贝技术来优化文件读写。
传统文件复制方式: 需要对文件在内存中进行四次拷贝。
零拷贝: 有两种方式, mmap和transfile
Java当中对零拷贝进行了封装, Mmap方式通过MappedByteBuffer对象进行 *** 作,而transfile通过FileChannel来进行 *** 作。
Mmap 适合比较小的文件,通常文件大小不要超过1.5G ~2G 之间。
Transfile没有文件大小限制。
RocketMQ当中使用Mmap方式来对他的文件进行读写。commitlog。 1G
在kafka当中,他的index日志文件也是通过mmap的方式来读写的。在其他日志文件当中,并没有使用零拷贝的方式。
kafka使用transfile方式将硬盘数据加载到网卡。
七、使用MQ如何保证分布式事务的最终一致性?
分布式事务:业务相关的多个 *** 作,保证他们同时成功或者同时失败。
最终一致性: 与之对应的就是强一致性
MQ中要保护事务的最终一致性,就需要做到两点
1、生产者要保证100%的消息投递。 事务消息机制
2、消费者这一端需要保证幂等消费。 唯一ID+ 业务自己实现幂等
分布式MQ的三种语义:
at least once
at most once
exactly once:
RocketMQ 并不能保证exactly once。商业版本当中提供了exactly once的实现机制。
kafka: 在最新版本的源码当中,提供了exactly once的demo。
RabbitMQ: erlang天生就成为了一种屏障。
八、让你设计一个MQ,你会如何设计?
两个误区: 1、 放飞自我,漫无边际。 2、纠结技术细节。
好的方式: 1、 从整体到细节,从业务场景到技术实现。2、以现有产品为基础。RocketMQ
答题思路: MQ作用、项目大概的样子。
1、实现一个单机的队列数据结构。 高效、可扩展。
2、将单机队列扩展成为分布式队列。- 分布式集群管理
3、基于Topic定制消息路由策略。- 发送者路由策略,消费者与队列对应关系,消费者路由策略
4、实现高效的网络通信。- Netty Http
5、规划日志文件,实现文件高效读写。- 零拷贝,顺序写。 服务重启后,快速还原运行现场。
6、定制高级功能,死信队列、延迟队列、事务消息等等。 – 贴合实际,随意发挥。
06缓存篇
## 一、为什么使用缓存?
1、高性能
2、高可用
二、什么是缓存穿透?缓存击穿?缓存雪崩?怎么解决?
1、缓存穿透: 缓存中查不到,数据库中也查不到。
解决方案: 1》 对参数进行合法性校验。2》将数据库中没有查到结果的数据也写入到缓存。这时要注意为了防止Redis被无用的Key占满,这一类缓存的有效期要设置得短一点。
3》 引入布隆过滤器,在访问Redis之前判断数据是否存在。 要注意布隆过滤器存在一定的误判率,并且,布隆过滤器只能加数据不能减数据。
2、缓存击穿:缓存中没有,数据库中有。一般是出现在存数数据初始化以及key过期了的情况。他的问题在于,重新写入缓存需要一定的时间,如果是在高并发场景下,过多的请求就会瞬间写到DB上,给DB造成很大的压力。
解决方案: 1》设置这个热点缓存永不过期。这时要注意在value当中包含一个逻辑上的过期时间,然后另起一个线程,定期重建这些缓存。
2》加载DB的时候,要防止并发。
3、缓存雪崩: 缓存大面积过期,导致请求都被转发到DB。
解决方案:1》把缓存的时效时间分散开。例如,在原有的统一失效时间基础上,增加一个随机值。
2》对热点数据设置永不过期。
三、如何保证Redis与数据库的数据一致?
当我们对数据进行修改的时候,到底是先删缓存,还是先写数据库?
1、如果先删缓存,再写数据库: 在高并发场景下,当第一个线程删除了缓存,还没有来得及写数据库,第二个线程来读取数据,会发现缓存中的数据为空,那就会去读数据库中的数据(旧值,脏数据),读完之后,把读到的结果写入缓存(此时,第一个线程已经将新的值写到缓存里面了),这样缓存中的值就会被覆盖为修改前的脏数据。
总结:在这种方式下,通常要求写 *** 作不会太频繁。
解决方案:1》先 *** 作缓存,但是不删除缓存。将缓存修改为一个特殊值(-999)。客户端读缓存时,发现是默认值,就休眠一小会,再去查一次Redis。 -》 特殊值对业务有侵入。 休眠时间,可能会多次重复,对性能有影响。
2》延时双删。 先删除缓存,然后再写数据库,休眠一小会,再次删除缓存。-》 如果数据写 *** 作很频繁,同样还是会有脏数据的问题。
2、先写数据库,再删缓存: 如果数据库写完了之后,缓存删除失败,数据就会不一致。
总结: 始终只能保证一定时间内的最终一致性。
解决方案: 1》给缓存设置一个过期时间 问题:过期时间内,缓存数据不会更新。
2》引入MQ,保证原子 *** 作。
解决方案:将热点数据缓存设置为永不过期,但是在value当中写入一个逻辑上的过期时间,另外起一个后台线程,扫描这些key,对于已逻辑上过期的缓存,进行删除。
四、如何设计一个分布式锁?如何对锁性能进行优化?
分布式锁的本质:就是在所有进程都能访问到的一个地方,设置一个锁资源,让这些进程都来竞争锁资源。数据库、zookeeper, Redis。。通常对于分布式锁,会要求响应快、性能高、与业务无关。
Redis实现分布式锁:SETNX key value:当key不存在时,就将key设置为value,并返回1。如果key存在,就返回0。EXPIRE key locktime: 设置key的有效时长。 DEL key: 删除。 GETSET key value: 先GET,再SET,先返回key对应的值,如果没有就返回空。然后再将key设置成value。
1、最简单的分布式锁: SETNX 加锁, DEL解锁。问题: 如果获取到锁的进程执行失败,他就永远不会主动解锁,那这个锁就被锁死了。
2、给锁设置过期时长: 问题: SETNX 和EXPIRE并不是原子性的,所以获取到锁的进程有可能还没有执行EXPIRE指令,就挂了,这时锁还是会被锁死。
3、将锁的内容设置为过期时间(客户端时间+过期时长),SETNX获取锁失败时,拿这个时间跟当前时间比对,如果是过期的锁,就先删除锁,再重新上锁。 问题: 在高并发场景下,会产生多个进程同时拿到锁的情况。
4、setNX失败后,获取锁上的时间戳,然后用getset,将自己的过期时间更新上去,并获取旧值。如果这个旧值,跟之前获得的时间戳是不一致的,就表示这个锁已经被其他进程占用了,自己就要放弃竞争锁。
public boolean tryLock(RedisnConnection conn){
long nowTime= System.currnetTimeMillis();
long expireTIme = nowTime+1000;
if(conn.SETNX(\"mykey\",expireTIme)==1){
conn.EXPIRE(\"mykey\",1000);
return true;
}else{
long oldVal = conn.get(\"mykey\");
if(oldVal != null && oldVal < nowTime){
long currentVal = conn.GETSET(\"mykey\",expireTime);
if(oldVal == curentVal){
conn.EXPIRE(\"mykey\",1000);
return true;
}
return false;
}
return false;
}
}
DEL
5、上面就形成了一个比较高效的分布式锁。分析一下,上面各种优化的根本问题在于SETNX和EXPIRE两个指令无法保证原子性。Redis2.6提供了直接执行lua脚本的方式,通过Lua脚本来保证原子性。redission。
五、Redis如何配置Key的过期时间?他的实现原理是什么?
redis设置key的过期时间: 1、 EXPIRE 。 2 SETEX
实现原理:
1、定期删除: 每隔一段时间,执行一次删除过期key的 *** 作。
2、懒汉式删除: 当使用get、getset等指令去获取数据时,判断key是否过期。过期后,就先把key删除,再执行后面的 *** 作。
Redis是将两种方式结合来使用。
懒汉式删除
定期删除:平衡执行频率和执行时长。
定期删除时会遍历每个database(默认16个),检查当前库中指定个数的key(默认是20个)。随机抽查这些key,如果有过期的,就删除。
程序中有一个全局变量记录到秒到了哪个数据库。
六、海量数据下,如何快速查找一条记录?
1、使用布隆过滤器,快速过滤不存在的记录。
使用Redis的bitmap结构来实现布隆过滤器。
2、在Redis中建立数据缓存。 – 将我们对Redis使用场景的理解尽量表达出来。
以普通字符串的形式来存储,(userId -> user.json)。 以一个hash来存储一条记录 (userId key-> username field-> , userAge->)。 以一个整的hash来存储所有的数据,UserInfo-> field就用userId , value就用user.json。一个hash最多能支持2^32-1(40多个亿)个键值对。
缓存击穿:对不存在的数据也建立key。这些key都是经过布隆过滤器过滤的,所以一般不会太多。
缓存过期:将热点数据设置成永不过期,定期重建缓存。 使用分布式锁重建缓存。
3、查询优化。
按槽位分配数据,
自己实现槽位计算,找到记录应该分配在哪台机器上,然后直接去目标机器上找。
07微服务篇
## 一、谈谈你对微服务的理解,微服务有哪些优缺点?
微服务是由Martin Fowler大师提出的。微服务是一种架构风格,通过将大型的单体应用划分为比较小的服务单元,从而降低整个系统的复杂度。
优点:
1、服务部署更灵活:每个应用都可以是一个独立的项目,可以独立部署,不依赖于其他服务,耦合性降低。
2、技术更新灵活:在大型单体应用中,技术要进行更新,往往是非常困难的。而微服务可以根据业务特点,灵活选择技术栈。
3、应用的性能得到提高: 大型单体应用中,往往启动就会成为一个很大的难关。而采用微服务之后,整个系统的性能是能够得到提高的。
4、更容易组合专门的团队: 在单体应用时,团队成员往往需要对系统的各个部分都要有深入的了解,门槛是很高的。而采用微服务之后,可以给每个微服务组建专门的团队。
5、代码复用: 很多底层服务可以以REST API的方式对外提供统一的服务,所有基础服务可以在整个微服务系统中通用。
缺点:
1、服务调用的复杂性提高了:网络问题、容错问题、负载问题、高并发问题。。。。。。
2、分布式事务:尽量不要使用微服务事务。
3、测试的难度提升了:
4、运维难度提升:单体架构只要维护一个环境,而到了微服务是很多个环境,并且运维方式还都不一样。所以对部署、监控、告警等要求就会变得非常困难。
二、SpringCloud和SpringCloudAlibaba都有哪些组件?都解决了什么问题?
SpringCloud:提供了构建微服务系统所需要的一组通用开发模式以及一系列快速实现这些开发模式的工具。
通常所说的SpringCloud是指SpringCloud NetFlix,他和SpringCloudAlibaba都是SpringCloud这一系列开发模式的具体实现。
SpringCloud NetFlix
SpringCloud Alibaba:
三、分布式事务如何处理?怎么保证事务一致性?
误区: 分布式事务 = Seata
分布式事务: 就是要将不同节点上的事务 *** 作,提供 *** 作原子性保证。同时成功或者同时失败。
分布式事务第一个要点就是要在原本没有直接关联的事务之间建立联系。
1、HTTP连接: 最大努力通知。 – 事后补偿。
2、 MQ : 事务消息机制。
3、Redis: 也可以定制出分布式事务机制。
4、Seata : 是通过TC来在多个事务之间建立联系。
两阶段:AT XA 就在于要锁资源。
三阶段:TCC 在两阶段的基础上增加一个准备阶段。在准备阶段是不锁资源的。
SAGA模式: 类似于熔断。 业务自己实现正向 *** 作和补偿 *** 作的逻辑。
四、怎么拆分微服务?怎样设计出高内聚、低耦合的微服务?有没有了解过DDD领域驱动设计?什么是中台?中台和微服务有什么关系?
拆分微服务的时候,为了尽量保证微服务的稳定,会有一些基本的准则:
1、微服务之间尽量不要有业务交叉。
2、微服务之前只能通过接口进行服务调用,而不能绕过接口直接访问对方的数据。
3、高内聚,低耦合。
高内聚低耦合,是一种从上而下指导微服务设计的方法。实现高内聚低耦合的工具主要有 同步的接口调用 和 异步的事件驱动 两种方式。
什么是DDD: 在2004年,由Eric Evans提出了, DDD是面对软件复杂之道。Domain-Driven- Design –Tackling Complexity in the Heart of Software
Martin Fowler – 贫血模型 ——》 贫血失忆症。 充血模型
MVC架构 -》 领域优先的四层架构
大泥团: 不利于微服务的拆分。大泥团结构拆分出来的微服务依然是泥团机构,当服务业务逐渐复杂,这个泥团又会膨胀成为大泥团。
DDD只是一种方法论,没有一个稳定的技术框架。DDD要求领域是跟技术无关、跟存储无关、跟通信无关。
中台这个概念是由阿里在2015年提出\”小前台,大中台\”战略思想。
superCell 《皇室战争》《部落冲突》
所谓中台,就是将各个业务线中可以复用的一些功能抽取出来,剥离个性,提取共性,形成一些可复用的组件。 盒马鲜生、团购
大体上,中台可以分为三类 业务中台、数据中台和技术中台。大数据杀熟-数据中台
电商 收银中台 支付风控中台
中台跟DDD结合: DDD会通过限界上下文将系统拆分成一个一个的领域, 而这种限界上下文,天生就成了中台之间的逻辑屏障。
DDD在技术与资源调度方面都能够给中台建设提供不错的指导。
DDD分为战略设计和战术设计。 上层的战略设计能够很好的指导中台划分,下层的战术设计能够很好的指导微服务搭建。
在目前阶段,DDD还大都处在小范围实验的阶段。
五、你的项目中是怎么保证微服务敏捷开发的?微服务的链路追踪、持续集成、AB发布要怎么做?
开发运维一体化。
敏捷开发: 目的就是为了提高团队的交付效率,快速迭代,快速试错。
每个月固定发布新版本,以分支的形式保存到代码仓库中。快速入职。任务面板、站立会议。团队人员灵活流动,同时形成各个专家代表。测试环境- 生产环境 -》 开发测试环境SIT、集成测试环境、压测环境STR、预投产环境、生产环境PRD。文档优先。晨会、周会、需求拆分会。
链路追踪:1、基于日志。 形成全局事务ID,落地到日志文件。 filebeat- logstash-Elasticsearch 形成大型报表。2、基于MQ,往往需要架构支持。经过流式计算形成一些可视化的结果。
持续集成:SpringBoot maven pom ->build -> shell ; Jenkins。
AB发布:1、蓝绿发布、红黑发布。 老版本和新版本是同时存在的。2、灰度发布、金丝雀发布。
08Spring底层篇
## 一、什么是Spring?谈谈你对IOC和AOP的理解。
Spring: 是一个企业级java应用框架,他的作用主要是简化软件的开发以及配置过程,简化项目部署环境。
Spring的有点:
1、Spring低侵入设计,对业务代码的污染非常低。
2、Spring的DI机制将对象之间的关系交由框架处理,减少组件的耦合。
3、Spring提供了AOP技术,支持将一些通用的功能进行集中式管理,从而提供更好的复用。
4、Spring对于主流框架提供了非常好的支持。
IOC就是控制反转,指创建对象的控制权转移给Spring来进行管理。简单来说,就是应用不用去new对象了,而全部交由Spring自动生产。
IOC有三种注入方式:1、 构造器注入 2、setter方法注入 3、根据注解注入。
AOP 面向切面。用于将那些与业务无关,但却对多个对象产生影响的公共行为。抽取并封装成一个可重用的模块。AOP的核心就是动态代理。JDK的动态代理 和 CGLIB动态代理。
二、Spring容器的启动流程是怎么样的?
使用AnnotationConfigApplicationContext 来跟踪一下启动流程:
this(); 初始化reader和scanner
scan(basePackages); 使用scanner组件扫描basePackage下的所有对象,将配置类的BeanDefinition注册到容器中。
refresh(); 刷新容器。
prepareRefresh 刷新前的预处理
obtainFreshBeanFactory: 获取在容器初始化时创建的BeanFactory
prepareBeanFactory: BeanFactory的预处理工作,会向容器中添加一些组件。
postProcessBeanFactory: 子类重写该方法,可以实现在BeanFactory创建并预处理完成后做进一步的设置。
invokeBeanFactoryPostProcessors: 在BeanFactory初始化之后执行BeanFactory的后处理器。
registerBeanPostProcessors: 向容器中注册Bean的后处理器,他的主要作用就是干预Spring初始化Bean的流程,完成代理、自动注入、循环依赖等这些功能。
initMessageSource: 初始化messagesource组件,主要用于国际化。
initApplicationEventMulticaster: 初始化事件分发器
onRefresh: 留给子容器,子类重写的方法,在容器刷新的时候可以自定义一些逻辑。
registerListeners: 注册监听器。
finishBeanFactoryInitialization: 完成BeanFactory的初始化,主要作用是初始化所有剩下的单例Bean。
finishRefresh: 完成整个容器的初始化,发布BeanFactory容器刷新完成的事件。
三、Spring框架中Bean的创建过程是怎样的?
首先,简单来说,Spring框架中的Bean经过四个阶段: 实例化 -》 属性赋值 -》 初始化 -》 销毁
然后: 具体来说,Spring中Bean 经过了以下几个步骤:
1、实例化: new xxx(); 两个时机: 1、当客户端向容器申请一个Bean时,2、当容器在初始化一个Bean时发现还需要依赖另一个Bean。 BeanDefinition 对象保存。-到底是new一个对象还是创建一个动态代理?
2、设置对象属性(依赖注入):Spring通过BeanDefinition找到对象依赖的其他对象,并将这些对象赋予当前对象。
3、处理Aware接口:Spring会检测对象是否实现了xxxAware接口,如果实现了,就会调用对应的方法。
BeanNameAware、BeanClassLoaderAware、BeanFactoryAware、ApplicationContextAware
4、BeanPostProcessor前置处理: 调用BeanPostProcessor的postProcessBeforeInitialization方法
5、InitializingBean: Spring检测对象如果实现了这个接口,就会执行他的afterPropertiesSet()方法,定制初始化逻辑。
6、init-method: 如果Spring发现Bean配置了这个属性,就会调用他的配置方法,执行初始化逻辑。@PostConstruct
7、BeanPostProcessor后置处理: 调用BeanPostProcessor的postProcessAfterInitialization方法
到这里,这个Bean的创建过程就完成了, Bean就可以正常使用了。
8、DisposableBean: 当Bean实现了这个接口,在对象销毁前就会调用destory()方法。
9、destroy-method: @PreDestroy
四、Spring框架中的Bean是线程安全的吗?如果线程不安全,要如何处理?
Spring容器本身没有提供Bean的线程安全策略,因此,也可以说Spring容器中的Bean不是线程安全的。
要如何处理线程安全问题,就要分情况来分析。
Spring中的作用域: 1、 sington 2、prototype: 为每个Bean请求创建给实例。 3、request:为每个request请求创建一个实例,请求完成后失效。 4、 session: 与request是类似的。 5、global-session:全局作用域。
对于线程安全问题:
1> 对于prototype作用域,每次都是生成一个新的对象,所以不存在线程安全问题。
2>sington作用域: 默认就是线程不完全的。但是对于开发中大部分的Bean,其实是无状态的,不需要保证线程安全。所以在平常的MVC开发中,是不会有线程安全问题的。
无状态表示这个实例没有属性对象, 不能保存数据,是不变的类。比如: controller、service、dao
有状态表示示例是有属性对象,可以保存数据,是线程不安全的, 比如 pojo.
但是如果要保证线程安全,可以将Bean的作用域改为prototype 比如像 Model View。
另外还可以采用ThreadLocal来解决线程安全问题。ThreadLocal为每个线程保存一个副本变量, 每个线程只 *** 作自己的副本变量。
五、Spring如何处理循环依赖问题?
循环依赖: 多个对象之间存在循环的引用关系,在初始化过程当中,就会出现\”先有蛋还是先有鸡\”的问题。
一种是使用@Lazy注解: 解决构造方法造成的循环依赖问题
另一种是使用三级缓存
一级缓存:缓存最终的单例池对象: private final Map singletonObjects = new ConcurrentHashMap(256);
二级缓存:缓存初始化的对象:private final Map earlySingletonObjects = new ConcurrentHashMap(16);
三级缓存:缓存对象的ObjectFactory: private final Map<String, ObjectFactory> singletonFactories = new HashMap(16);
对于对象之间的普通引用,二级缓存会保存new出来的不完整对象,这样当单例池中找到不依赖的属性时,就可以先从二级缓存中获取到不完整对象,完成对象创建,在后续的依赖注入过程中,将单例池中对象的引用关系调整完成。
三级缓存:如果引用的对象配置了AOP,那在单例池中最终就会需要注入动态代理对象,而不是原对象。而生成动态代理是要在对象初始化完成之后才开始的。于是Spring增加三级缓存,保存所有对象的动态代理配置信息。在发现有循环依赖时,将这个对象的动态代理信息获取出来,提前进行AOP,生成动态代理。
核心代码就在DefaultSingletonBeanRegistry的getSingleton方法当中。
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// Quick check for existing instance without full singleton lock
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
synchronized (this.singletonObjects) {
// Consistent creation of early reference within full singleton lock
singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null) {
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
}
}
return singletonObject;
}
六、Spring如何处理事务?
Spring当中支持编程式事务管理和声明式事务管理两种方式:
1、编程式事务可以使用TransactionTemplate。
2、声明式事务: 是Spring在AOP基础上提供的事务实现机制。他的最大优点就是不需要在业务代码中添加事务管理的代码,只需要在配置文件中做相关的事务规则声明就可以了。但是声明式事务只能针对方法级别,无法控制代码级别的事务管理。
Spring中对事务定义了不同的传播级别: Propagation
1、 PROPAGATION_REQUIRED:默认传播行为。 如果当前没有事务,就创建一个新事务,如果当前存在事务,就加入到事务中。
2、PROPAGATION_SUPPORTS: 如果当前存在事务,就加入到该事务。如果当前不存在事务,就以非事务方式运行。
3、PROPAGATION_MANDATORY: 如果当前存在事务,就加入该事务。如果当前不存在事务,就抛出异常。
4、PROPAGATION_REQUIRES_NEW: 无论当前存不存在事务,都创建新事务进行执行。
5、PROPAGATION_NOT_SUPPORTED: 以非事务方式运行。如果当前存在事务,就将当前事务挂起。
6、PROPAGATION_NEVER : 以非事务方式运行。如果当前存在事务,就抛出异常。
7、PROPAGATION_NESTED: 如果当前存在事务,则在嵌套事务内执行;如果当前没有事务,则按REQUEIRED属性执行。
Spring中事务的隔离级别:
1、ISOLATION_DEFAULT: 使用数据库默认的事务隔离级别。
2、ISOLATION_READ_UNCOMMITTED: 读未提交。允许事务在执行过程中,读取其他事务未提交的数据。
3、ISOLATION_READ_COMMITTED: 读已提交。允许事务在执行过程中,读取其他事务已经提交的数据。
4、ISOLATION_REPEATABLE_READ: 可重复读。 在同一个事务内,任意时刻的查询结果是一致的。
5、ISOLATION_SERIALIZABLE: 所有事务依次执行。
七、SpringMVC中的控制器是不是单例模式?如果是,如何保证线程安全?
控制器是单例模式。
单例模式下就会有线程安全问题。
Spring中保证线程安全的方法
1、将scop设置成非singleton。 prototype, request。
2、最好的方式是将控制器设计成无状态模式。在控制器中,不要携带数据。但是可以引用无状态的service和dao。
09MySQL数据库篇
## 一、MySQL有哪几种数据存储引擎?有什么区别?
MySQL中通过show ENGINES指令可以看到所有支持的数据库存储引擎。 最为常用的就是MyISAM 和InnoDB 两种。
MyISAM和InnDB的区别:
1、存储文件。 MyISAM每个表有两个文件。 MYD和MYISAM文件。 MYD是数据文件。 MYI是索引文件。 而InnDB每个表只有一个文件,idb。
2、InnoDB支持事务,支持行级锁,支持外键。
3、InnoDB支持XA事务
4、InnoDB支持savePoints
二、什么是脏读、幻读、不可重复读?要怎么处理?
这些问题都是MySQL进行事务并发控制时经常遇到的问题。
脏读: 在事务进行过程中,读到了其他事务未提交的数据。
不可重复读: 在一个事务过程中,多次查询的结果不一致。
幻读: 在一个事务过程中,用同样的 *** 作查询数据,得到的记录数不相同。
处理的方式有很多种:加锁、事务隔离、MVCC
加锁:
1、脏读:在修改时加排他锁,直到事务提交才释放。读取时加共享锁,读完释放锁。
2、不可重复读: 读数据时加共享锁,写数据时加排他锁。
3、幻读: 加范围锁。
三、事务的基本特性和隔离级别有哪些?
事务: 表示多个数据 *** 作组成一个完整的事务单元,这个事务内的所有数据 *** 作要么同时成功,要么同时失败。
事务的特性:ACID
1、原子性:事务是不可分割的,要么完全成功,要么完全失败。
2、一致性:事务无论是完成还是失败,都必须保持事务内 *** 作的一致性。当失败时,都要对前面的 *** 作进行回滚,不管中途是否成功。
3、隔离性:当多个事务 *** 作一个数据的时候,为防止数据损坏,需要将每个事务进行隔离,互相不干扰。
4、持久性: 事务开始就不会终止。他的结果不受其他外在因素的影响。
事务的隔离级别:SHOW VARIABLES like ‘transaction%’
设置隔离级别: set transaction level xxx 设置下次事务的隔离级别。
set session transaction level xxx 设置当前会话的事务隔离级别
set global transaction level xxx 设置全局事务隔离级别
MySQL当中有五种隔离级别
NONE : 不使用事务。
READ UNCOMMITED: 允许脏读
READ COMMITED: 防止脏读,最常用的隔离级别
REPEATABLE READ:防止脏读和不可重复读。MYSQL默认
SERIALIZABLE: 事务串行,可以防止脏读、幻读,不可重复度。
五种隔离级别,级别越高,事务的安全性是更高的,但是,事务的并性能也就会越低。
四、MySQL的锁有哪些?什么是间隙锁?
从锁的粒度来区分
1、行锁:加锁粒度小,但是加锁资源开销比较大。 InnDB支持。
共享锁: 读锁。多个事务可以对同一个数据共享同一把锁。持有锁的事务都可以访问数据,但是只能读不能修改。select xxx LOCK IN SHARE MODE。
排他锁: 写锁。只有一个事务能够获得排他锁,其他事务都不能获取该行的锁。InnoDB会对update\\delete\\insert语句自动添加排他锁。SELECT xxx FOR UPDATE。
自增锁: 通常是针对MySQL当中的自增字段。如果有事务回滚这种情况,数据会回滚,但是自增序列不会回滚。
2、表锁:加锁粒度大,加锁资源开销比较小。MyISAM和InnoDB都支持。
表共享读锁
表排他写锁
意向锁:是InnoDB自动添加的一种锁,不需要用户干预。
3、全局锁: Flush tables with read lock 。 加锁之后整个数据库实例都处于只读状态。所有的数据变更 *** 作都会被挂起。一般用于全库备份的时候。
常见的锁算法: user: userid ( 1,4,9) update user set xxx where userid=5; REPEATABLE READ 间隙锁锁住(5,9)
1、记录锁:锁一条具体的数据。
2、间隙锁:RR隔离级别下,会加间隙锁。锁一定的范围,而不锁具体的记录。是为了防止产生幻读。(-xx,1)(1,4)(4,9)(9,xxx)
3、Next-key : 间隙锁+右记录锁。(-xx,1](1,4](4,9](9,xxx)
五、MySQL的索引结构是什么样的?聚簇索引和非聚簇索引又是什么?
二叉树 -》 AVL树 -》 红黑树 -》 B-树 -》 B+树
二叉树: 每个节点最多只有两个子节点, 左边的子节点都比当前节点小,右边的子节点都比当前节点大。
AVL树: 树中任意节点的两个子树的高度差最大为1
红黑树:1、每个节点都是红色或者黑色。2 根节点是黑色。3 每个叶子节点都是黑色的空节点。4 红色节点的父子节点都必须是褐色。5 从任一节点到其每个叶子节点的所有路径都包含相同的黑色节点。
B-树: 1、B-树的每个非叶子节点的子节点个数都不会超过D(这个D就是B-树的阶)2、所有的叶子节点都在同一层。3.所有节点关键字都是按照递增顺序排列。
B+树: 1、非叶子节点不存储数据,只进行数据索引。2、所有数据都存储在叶子节点当中。3、每个叶子节点都存有相邻叶子节点的指针。4、叶子节点按照本身关键字从小到大排序。
聚簇索引就是数据和索引是在一起的。
MyISAM使用的是非聚簇索引,树的子节点上的data不是数据本身,而是数据存放的地址。InnoDB采用的是聚簇索引,树的叶子节点上的data就是数据本身。
聚簇索引的数据物理存放顺序和索引顺序是一致的,所以一个表当中只能有一个聚簇索引,而非聚簇索引可以有多个。
InnoDB中,如果表定义了PK,那PK就是聚簇索引。 如果没有PK,就会找第一个非空的unique列作为聚簇索引。否则,InnoDB会创建一个隐藏的row-id作为聚簇索引。
MySQL的覆盖索引和回表
如果只需要在一颗索引树上就可以获取SQL所需要的所有列,就不需要再回表查询,这样查询速度就可以更快。
实现索引覆盖最简单的方式就是将要查询的字段,全部建立到联合索引当中。
user(PK id , name ,sex)
select count(name) from user ; -> 在name 字段上建立一个索引。
select id , name ,sex from user; -> 将name上的索引升级成为(name,sex)的联合索引。
六、MySQL的集群是如何搭建的?读写分离是怎么做的?
MySQL主从集群的搭建原理:
MySQL通过将主节点的Binlog同步给从节点完成主从之间的数据同步。
MySQL的主从集群只会将binlog从主节点同步到从节点,而不会反过来同步。由此也就引申出了读写分离的问题。
因为要保证主从之间的数据一致,写数据的 *** 作只能在主节点完成, 而读数据的 *** 作,可以在主节点或者从节点上完成。
七、谈谈如何对MySQL进行分库分表?多大数据量需要进行分库分表?分库分表的方式和分片策略由哪些?分库分表后,SQL语句的执行流程是怎样的?
什么是分库分表? 就是当表中的数据量过大时,整个查询效率就会降低得非常明显。这时为了提升查询效率,就要将一个表中的数据分散到多个数据库的多个表当中。
分库分表最常用的组件: Mycat\\ ShardingSphere
数据分片的方式有垂直分片和水平分片。垂直分片就是从业务角度将不同的表拆分到不同的库中,能够解决数据库数据文件过大的问题,但是不能从根本上解决查询问题。水平分片就是从数据角度将一个表中的数据拆分到不同的库或表中,这样可以从根本上解决数据量过大造成的查询效率低的问题。
有非常多的分片策略,比如 取模、按时间、按枚举值。。。。
阿里提供的开发手册当中,建议:一个表的数据量超过500W或者数据文件超过2G,就要考虑分库分表了。
分库分表后的执行流程:
一个user表,按照userid进行了分片,然后我需要按照sex字段去查,这要怎么查?强制指定只查一个数据库,要怎么做?查询结果按照userid来排序,要怎么排?
分库分表的问题: 垮库查询、跨库排序、分布式事务、公共表、主键重复。。。。。
10搜索引擎篇
一、什么是倒排索引?有什么好处?
索引: 从ID到内容。
倒排索引: 从内容到ID。好处: 比较适合做关键字检索。 可以控制数据的总量。提高查询效率。
搜索引擎为什么比MySQL查询快? lucence
文章 -》 term ->排序 term dictionary -> term index -》 Posting List -> [文章ID ,[在文章中出现的偏移量],权重 ]TFIDF
二、ES了解多少?说说你们公司的ES集群架构。
ES: 是一个基于Lucene框架的搜索引擎产品。you know for search。提供了Restful风格的 *** 作接口。 ELK
Lucene:是一个非常高效的全文检索引擎框架。java jar
ES的一些核心概念:
1、索引 index : 关系型数据库中的 table
2、文档 document : row
3、字段 field text\\keyword\\byte : 列
4、映射Mapping : Schema。
5、查询方式 DSL : SQL ES的新版本也支持SQL
6、分片 sharding 和 副本 replicas: index都是由sharding组成的。每个sharding都有一个或多个备份。 ES集群健康状态:
ES的使用场景。ES可以用在大数据量的搜索场景下,另外ES也有很强大的计算能力。用户画像
三、如何进行中文分词?用过哪些分词器?
IK分词器。
四、ES写入数据的工作原理是什么?
1、客户端发写数据的请求时,可以发往任意节点。这个节点就会成为coordinating node协调节点。
2、计算的点文档要写入的分片:计算时就采用hash取模的方式来计算。
3、协调节点就会进行路由,将请求转发给对应的primary sharding所在的datanode。
4、datanode节点上的primary sharding处理请求,写入数据到索引库,并且将数据同步到对应的replica sharding
5、等primary sharding 和 replica sharding都保存好文档了之后,返回客户端响应。
五、ES查询数据的工作原理是什么?
1、客户端发请求可发给任意节点,这个节点就成为协调节点
2、协调节点将查询请求广播到每一个数据节点,这些数据节点的分片就会处理改查询请求。
3、每个分片进行数据查询,将符合条件的数据放在一个队列当中,并将这些数据的文档ID、节点信息、分片信息都返回给协调节点。
4、由协调节点将所有的结果进行汇总,并排序。
5、协调节点向包含这些文档ID的分片发送get请求,对应的分片将文档数据返回给协调节点,最后协调节点将数据整合返回给客户端。
六、ES部署时,要如何进行优化?
1、集群部署优化。
调整ES的一些重要参数。path.data目录尽量使用SSD。定时JVM堆内存大小。
关于ES的参数,大部分情况下是不需要调优的,如果有性能问题,最好的办法是安排更合理的sharding布局并且增加节点数量。
2、更合理的sharding布局:
让sharding和对应的replica sharding尽量在同一个机房。
3、Linux服务器上的一些优化策略:
不要用root用户;修改虚拟内存大小;修改普通用户可以创建的最大线程数。
ES生态: ELK日志收集解决方案- filebeat(读log日志)-> logstash -> ElasticSearch -> kibana、Grafana、自研的报表平台
11安全验证篇
一、什么是认证和授权?如何设计一个权限认证框架?
认证: 就是对系统访问者的身份进行确认。 用户名密码登录、 二维码登录、手机短信登录、指纹、刷脸。。。
授权:就是对系统访问者的行为进行控制。授权通常是在认证之后,对系统内的用户隐私数据进行保护。后台接口访问权限、前台控件的访问权限。
RBAC模型: 主体 -》 角色 -》 资源 -》访问系统的行为。
认证和授权也是对一个权限认证框架进行扩展的两个主要的方面。
二、Cookie和Session有什么区别?
三、如果没有Cookie,Session还能进行身份验证吗?
当服务器tomcat第一次接收到客户端的请求时,会开辟一块独立的session空间,建立一个session对象,同时会生成一个session id,通过响应头的方式保存到客户端浏览器的cookie当中。以后客户端的每次请求,都会在请求头部带上这个session id,这样就可以对应上服务端的一些会话的相关信息,比如用户的登录状态。
如果没有客户端的Cookie,Session是无法进行身份验证的。
当服务端从单体应用升级为分布式之后,cookie+session这种机制要怎么扩展?
1、session黏贴: 在负载均衡中,通过一个机制保证同一个客户端的所有请求都会转发到同一个tomcat实例当中。问题: 当这个tomcat实例出现问题之后,请求就会被转发到其他实例,这时候用户的session信息就丢了。
2、session复制: 当一个tomcat实例上保存了session信息后,主动将session 复制到集群中的其他实例。问题: 复制是需要时间的,在复制过程中,容易产生session信息丢失。
3、session共享: 就是将服务端的session信息保存到一个第三方中,比如Redis。
四、什么是CSRF攻击?如何防止?
CSRF: Cross Site Requst Forgery 跨站请求伪造
一个正常的请求会将合法用户的session id保存到浏览器的cookie。这时候,如果用户在浏览器中打来另一个tab页, 那这个tab页也是可以获得浏览器的cookie。黑客就可以利用这个cookie信息进行攻击。
攻击过程:
1、某银行网站A可以以GET请求的方式发起转账 *** 作。 www.xxx.com/transfor.do?accountNum=100&money=1000 accountNum表示目标账户。这个请求肯定是需要登录才可以正常访问的。
2、攻击者在某个论坛或者网站上,上传一个图片,链接地址是 www.xxx.com/transfer.do?accountNum=888&money=10000 其中这个accountNum就是攻击者自己的银行账户。
3、如果有一个用户,登录了银行网站,然后又打开浏览器的另一个tab页,点击了这个图片。这时,银行就会受理到一个带了正确cookie的请求,就会完成转账。用户的钱就被盗了。
CSRF方式方式:
1、尽量使用POST请求,限制GET请求。POST请求可以带请求体,攻击者就不容易伪造出请求。
2、将cookie设置为HttpOnly : respose.setHeader(“Set-Cookie”,“cookiename=cookievalue;HttpOnly”)。
3、增加token;
在请求中放入一个攻击者无法伪造的信息,并且该信息不存在于cookie当中。
这也是Spring Security框架中采用的防范方式。
五、什么是OAuth2.0协议?有哪几种认证方式?什么是JWT令牌?和普通令牌有什么区别?
OAuth2.0是一个开放标准,允许用户授权第三方应用程序访问他们存储在另外的服务提供者上的信息,而不需要将用户名和密码提供给第三方应用或分享他们数据的所有内容。
OAuth2.0协议的认证流程,简单理解,就是允许我们将之前的授权和认证过程交给一个独立的第三方进行担保。
OAuth2.0协议有四种认证方式:
1、授权码模式
2、简化模式
3、密码模式
4、客户端模式
在梳理OAuth2.0协议流程的过程中,其实有一个主线,就是三方参与者之家的信任程度。
普通令牌: b9f2eaa1-8715-4f03-86c7-06bf757a5f7c
普通令牌只是一个随机的字符串,没有特殊的意义。这就意味着,当客户带上令牌去访问应用的接口时,应用本身无法判断这个令牌是否正确,他就需要到授权服务器上去判断令牌是否有效。在高并发场景下,检查令牌的网络请求就有可能成为一个性能瓶颈。
改良的方式就是JWT令牌。将令牌对应的相关信息全部冗余到令牌本身,这样资源服务器就不再需要发送请求给授权服务器去检查令牌了,他自己就可以读取到令牌的授权信息。JWT令牌的本质就是一个加密的字符串!!
JWT令牌: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsic2FsYXJ5Il0sInVzZXJfbmFtZSI6ImFkbWluIiwic2NvcGUiOlsiYWxsIl0sImV4cCI6MTYxNjY3MjM3OCwiYXV0aG9yaXRpZXMiOlsibW9iaWxlIiwic2FsYXJ5Il0sImp0aSI6ImI1MDg2OWE0LTIzZmEtNDg2Yy1hZGJlLTljNTlmMjRiMDY4YSIsImNsaWVudF9pZCI6ImMxIn0.tJ5d7RBKPj8d6w7826OqS6_2pDf_ZXvwkJHMO2uPVAg
六、什么是SSO?与OAuth2.0有什么关系?
OAuth2.0的使用场景通常称为联合登录。 一处注册,多处使用。
SSO Single Sign On 单点登录。 一处登录,多处同时登录。
SSO的实现关键是将Session信息集中存储。Spring Security
七、如何设计一个开放授权平台?
开放授权平台也可以按照认证和授权两个方向来梳理。
1、认证: 就可以按照OAuth2.0协议来规划认证的过程。
2、授权: 首先需要待接入的第三方应用在开放授权平台进行注册,注册需要提供几个必要的信息 clintID, 消息推送地址,密钥(一对公私钥,私钥由授权平台自己保存,公钥分发给第三方应用)。
然后,第三方应用引导客户发起请求时,采用公钥进行参数加密,授权开放平台使用对应的私钥解密。
接下来:授权开放平台同步响应第三方应用的只是消息是否处理成功的结果。而真正的业务数据由授权开放平台异步推动给第三方应用预留的推送地址。
T令牌?和普通令牌有什么区别?
OAuth2.0是一个开放标准,允许用户授权第三方应用程序访问他们存储在另外的服务提供者上的信息,而不需要将用户名和密码提供给第三方应用或分享他们数据的所有内容。
OAuth2.0协议的认证流程,简单理解,就是允许我们将之前的授权和认证过程交给一个独立的第三方进行担保。
OAuth2.0协议有四种认证方式:
1、授权码模式
[外链图片转存中…(img-NZMW7nAI-1652494499035)]
2、简化模式
[外链图片转存中…(img-OOt730tq-1652494499036)]
3、密码模式
[外链图片转存中…(img-LHACbAhf-1652494499038)]
4、客户端模式
[外链图片转存中…(img-b0ybrhT3-1652494499039)]
在梳理OAuth2.0协议流程的过程中,其实有一个主线,就是三方参与者之家的信任程度。
普通令牌: b9f2eaa1-8715-4f03-86c7-06bf757a5f7c
普通令牌只是一个随机的字符串,没有特殊的意义。这就意味着,当客户带上令牌去访问应用的接口时,应用本身无法判断这个令牌是否正确,他就需要到授权服务器上去判断令牌是否有效。在高并发场景下,检查令牌的网络请求就有可能成为一个性能瓶颈。
改良的方式就是JWT令牌。将令牌对应的相关信息全部冗余到令牌本身,这样资源服务器就不再需要发送请求给授权服务器去检查令牌了,他自己就可以读取到令牌的授权信息。JWT令牌的本质就是一个加密的字符串!!
JWT令牌: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsic2FsYXJ5Il0sInVzZXJfbmFtZSI6ImFkbWluIiwic2NvcGUiOlsiYWxsIl0sImV4cCI6MTYxNjY3MjM3OCwiYXV0aG9yaXRpZXMiOlsibW9iaWxlIiwic2FsYXJ5Il0sImp0aSI6ImI1MDg2OWE0LTIzZmEtNDg2Yy1hZGJlLTljNTlmMjRiMDY4YSIsImNsaWVudF9pZCI6ImMxIn0.tJ5d7RBKPj8d6w7826OqS6_2pDf_ZXvwkJHMO2uPVAg
六、什么是SSO?与OAuth2.0有什么关系?
OAuth2.0的使用场景通常称为联合登录。 一处注册,多处使用。
SSO Single Sign On 单点登录。 一处登录,多处同时登录。
SSO的实现关键是将Session信息集中存储。Spring Security
七、如何设计一个开放授权平台?
开放授权平台也可以按照认证和授权两个方向来梳理。
1、认证: 就可以按照OAuth2.0协议来规划认证的过程。
2、授权: 首先需要待接入的第三方应用在开放授权平台进行注册,注册需要提供几个必要的信息 clintID, 消息推送地址,密钥(一对公私钥,私钥由授权平台自己保存,公钥分发给第三方应用)。
然后,第三方应用引导客户发起请求时,采用公钥进行参数加密,授权开放平台使用对应的私钥解密。
接下来:授权开放平台同步响应第三方应用的只是消息是否处理成功的结果。而真正的业务数据由授权开放平台异步推动给第三方应用预留的推送地址。
请登录后查看评论内容