2022--学习笔记

java基础和jvm看:https://github.com/Snailclimb/JavaGuide
推荐:https://learn.lianglianglee.com/

为什么用设计模式
    为了代码的解耦合和代码的拓展性和重用性,保证代码的可靠性
    
设计模式的分类3
    总体来说设计模式分为三大类:
    创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
    结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
    行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。

设计模式原则(总结就是开闭原则,抽象化、解耦化【各个功能做自己的事】、聚合化):
    开闭原则:对拓展开放、对修改关闭
    里氏代换原则(傻逼名称):“开-闭”原则的补充,任何基类可以出现的地方,子类一定可以出现
    依赖倒转原则:针对接口编程,依赖于抽象而不依赖于具体
    接口隔离原则:各个接口功能相对独立
    迪米特法则(又是一个外国傻逼):实体间独立性
    合成复用原则:尽量使用合成/聚合的方式,而不是使用继承
    总结:尽量使得修改关闭,新增开发,依赖与抽象而不依赖于具体,实体间应该独立,且基类可以出现的地方,子类一定可以出现,尽量采用合成复用的方式而不是进行抽象

什么情况使用设计模式、设计模式有什么缺点:
    首先,什么是设计模式,设计我们应该清楚设计模式是做什么的,设计模式是为了能够使代码解耦合和高拓展性

1.单例
    1.构造方法私有化
    2.编写getInstant方法并静态化
    3.创建一个私有对象
2.工厂模式
    新建一个接口
    新建n个不同的接口实现类
    创建fatory工厂,通过制定的参数标识采用if-else模式判断需要使用哪个实现
2.1.抽象工厂模式
    简单的工厂模式是对一种工厂的抽象,而抽象工厂模式是对多个工厂的抽象

3.代理模式(试算拦截各个调用方法的响应时间统计)
    静态代理:
    定义一个接口及其实现类 
    创建一个代理类同样实现这个接口 
    将目标对象注入进代理类,然后在代理类的对应方法调用目标类中的对应方法。这样的话,我们就可以通过代理类屏蔽对目标对象的访问,并且可以在目标方法执行前后做一些自己想做的事情。
    动态代理
        jdk proxy
            定义一个接口及其实现类; 
            自定义 InvocationHandler 并重写invoke方法,在 invoke 方法中我们会调用原生方法(被代理类的方法)并自定义一些处理逻辑; 
            通过 Proxy.newProxyInstance(ClassLoader loader,Class[] interfaces,InvocationHandler h) 方法创建代理对象
        CGLIB 动态代理
        定义一个类; 
        自定义 MethodInterceptor 并重写 intercept 方法,intercept 用于拦截增强被代理类的方法,和 JDK 动态代理中的 invoke 方法类似; 
        通过 Enhancer 类的 create()创建代理类;

4.策略模式
    新建Strategy策略接口
    实现n个策略实现类
    新建Context类,类中有策略成员,设置策略成员方法,调用策略处理方法
5.观察者模式
    新建观察者Subject主题接口,接口中有新增、修改通知、删除方法
    新建观察者Observer订阅人接口,接口中有接收变动通知方法
    新建主题实现类,类中实现方法,增加Observer队列,在修改通知接口中依次取出队列的值使用接收变动通知方法通知Observer
    新建具体订阅人实现类,实现具体订阅业务
    (java自带的观察者模式,主题继承Observable类,其他步骤不变)
6.原型模式(Prototype)

7.模板方法模式(Template Method)
    抽象类,存在一个抽象方法和编写调用抽象方法的方法
    编写一个类继承抽象类,覆盖抽象类的抽象方法
    使用:实例化该类,用父类接收其实例进行调用
    
8.适配器模式(Adapter),适用与去除if-else结构
    适配器模式涉及3个角色:
    源(Adaptee):需要被适配的对象或类型,相当于插头。可以是接口,可以是类,也可以是对象
    适配器(Adapter):连接目标和源的中间对象,相当于插头转换器。
    目标(Target):期待得到的目标,相当于插座。
    Adapter拥有Adaptee源并实现Target接口
//对象适配器类
class ObjectAdapter implements Target
{
    private Adaptee adaptee;
    public ObjectAdapter(Adaptee adaptee)
    {
        this.adaptee=adaptee;
    }
    public void request()
    {
        adaptee.specificRequest();
    }
}

//类适配器类
//目标接口
interface Target
{
    public void request();
}
//适配者接口
class Adaptee
{
    public void specificRequest()
    {       
        System.out.println("适配者中的业务代码被调用!");
    }
}
class ClassAdapter extends Adaptee implements Target
{
    public void request()
    {
        specificRequest();
    }
}

9.装饰器模式(Decorator)

10.责任链模式(车辆违章处理)
    1.处理短租订单违章 2.处理分时订单违章 3.处理第三方订单的违章 4.处理工单的违章
    
11.建造者
    区别于工厂,他是工厂的一种管理
    
12.策略模式与工厂模式的区别
    普通的工厂是采用factory写if-else结构去创建并返回对象使用,例子,如发送sender功能,可以有邮件发送也可以有短信发送,然后通过工厂去封装,通过if-else得到对应的对象使用,缺点不够灵活,要写if-else
    策略模式注重于行为,决定权在用户,如计算器中简单的加法、减法使用不同的策略,实现方式可以写一个策略方法或者新增一个hashMap容器,通过传进来不同的策略,去做一个计算,然后外部你想使用加法的算法器还是减法的算法器
    ,直接new不需要工厂就可以使用,实现的方法都在算法器当中    
    通俗讲:工厂的通过条件实例化类,策略是用户主动实例化类(既工厂是程序返回一个对象进行使用,而策略是指定一个对象进行使用)

分布式事务的解决方法:失败重试、分布式事务(2/3阶段提交)、消息一致性幂等校验、兜底核对(定时核对数据的一致性并手动及自动数据修复)

分布式事务具体框架:seat

分布式框架数据一致性解决方案:TCC(2pc的思想)
    Try:主要是数据的校验或者资源的预留
  Confirm:确认真正执行的任务,只操作Try阶段预留的资源
  Cancel:取消执行,释放Try阶段预留的资源。

分布式事务框架TCC模式下在出现的问题:(核心的解决方案就是增加事务状态控制表)
    幂等
        问题:TC重复调用二阶段
        解决:事务状态控制记录作为控制手段,只有存在INIT记录时才执行,存在CONFIRMED/ROLLBACKED记录时不再执行
    空回滚
        问题:TC回滚事务调用二阶段,但一阶段尚未执行
        解决:事务状态控制记录作为控制手段,无记录时即为空回滚
    资源悬挂
        问题:TC回滚事务调用二阶段完成空回滚后,一阶段执行成功
        解决:事务状态控制记录作为控制手段,二阶段发现无记录时插入记录,一阶段执行时检查记录是否存在

网络
何为io
    数据从用户空间和内核空间相互转化的一个输入输出过程
计算机结构分为:控制器、运算器、存储器、输入输出设备
有哪些IO模型:同步阻塞、同步非阻塞(通过轮询时间片操作,避免了一直阻塞)、多路复用、异步IO、信号驱动IO
    同步阻塞:应用程序发起 read 调用后,会一直阻塞,直到内核把数据拷贝到用户空间
    同步非阻塞:基于轮询,而不基于多路复用器,基于用户层面,非阻塞IO不会交出CPU,而会一直占用CPU
    多路复用:基于操作系统的支持,使用多路复用器,由操作系统完成查看数据是否就绪,而不是在用户态进行判断
    异步IO:异步IO是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作

同步阻塞:传统的socket操作
同步非阻塞:基于用户线程,没有用到selector,而是轮询询问socket状态
    例子:
    while(true){
        data = socket.read();
        if(data!= error){ 处理数据break; }
    }
多路复用:基于内核(本质是一个或者多个线程管理一堆socket)
    复用器原理:根据文件描述符fds判断,将fds传递到内核进行监听,内核根据传进来的fds文件描述符是否是有效的,然后进行判断
    select:基于数组,fd_set是固定大小的缓冲区bitmap,默认1024
    poll:基于链表,与select不同的是,FD_SETSIZE没有固定的数值,可以不是1024,也可以是2048
    select和poll的缺点:每次都要重新重复传递fds(内核开辟空间),每次内核被调用了之后,针对这次调用,触发了一个遍历fds全量的复杂度,时间复杂度为O(n)
    epoll:基于hash表,将fd_set进行排序,有数据进来的放在最前面,并且传送当前已有数据的通道数量,循环遍历当前已准备好的通道即可,如 fd1、fd2、fd3、fd4、fd5,此时如果3、4通道有数据进来,则会变成fd3、fd4、fd1、fd2、fd5,此时for(fd<2;fd in fds)
    问题:什么场景下使用哪种复用器:
        Java对复用器做了一层封装,暴露给我们开发人员的统一是selector,当我们app在运行的时候,需要根据实际的os平台以及os版本,帮我们选择一个最合适的复用器
        哪里调用了复用器:
        在调用selector.select()时调用,根据os平台的不同,使用不同的复用器,在epoll情况下,对应的是epoll_wait方法,在select情况下,对应于select()方法,在poll情况下,对应于poll()方法
    例子:Java NIO实际上就是多路复用IO
        while (true) {
            //这里会去询问是否有数据就绪,会进行阻塞,即当注册的事件到达时,方法返回;否则,该方法会一直阻塞
            selector.select();
            // 当前事件来的时候的迭代器
            Iterator ite = this.selector.selectedKeys().iterator();
            while (ite.hasNext()) {
                SelectionKey key = (SelectionKey) ite.next();
                // 删除已选的key,以防重复处理
                ite.remove();
                // 客户端请求连接事件
                ......
            }
        }
异步io:基于内核回调机制,整个完整的io操作都是异步的,原理是在内核层使用线程异步(非应用层面实现的线程异步,应用层实现的叫异步处理,不能称之为异步io)
服务端:AsynchronousServerSocketChannel--CompletionHandler/Future接口
客户端:AsynchronousSocketChannel--socket.read/socket.write--completed/Future

信号驱动:基于Signal类,Signal.handle()方法用户注册信号处理器,Signal.raise用于激发信号,发起时,socket注册一个信号函数,用户线程继续执行,就绪时,用户线程接收到信号之后,便在信号函数中调用IO读写操作来进行实际的IO请求操作

完整的IO操作:
    查看数据是否就绪(异步使用CompletionHandler或者Future查询是否就绪)
    进行数据拷贝(内核将数据拷贝到用户线程)

异步和同步:针对的是数据拷贝,针对用户线程和内核的交互而言,进行read/write调用的时候是否需等待完成并返回对应进程,阻塞IO、非阻塞IO、IO复用、信号驱动IO都是同步IO,如果不阻塞,而是操作系统帮你做完IO操作再将结果返回给你,那么就是异步IO
    同步:当用户发出IO请求操作之后,如果数据没有就绪,需要通过用户线程或者内核不断地去轮询数据是否就绪,当数据就绪时,再将数据从内核拷贝到用户线程
    异步:IO操作的两个阶段都是由内核自动完成,然后发送通知告知用户线程IO操作已经完成。也就是说在异步IO中,不会对用户线程产生任何阻塞
    
阻塞和非阻塞:操作系统os层面,进行read和write时是否进行阻塞
    阻塞:调用方法等待返回,如果数据没有就绪,则会一直在那等待,比如调用read方法
    非阻塞:调用方法立即返回,如果数据没有就绪,则会返回一个标志信息告知用户线程当前要读的数据没有就绪,比如read调用会告诉当前用户线程一个标志位,是就绪还是未就绪
    例如AIO中:
        客户端发送消息,使用客户端:AsynchronousSocketChannel的socket使用CompletionHandler异步发送、异步读取
        服务端接收消息,使用服务端端:AsynchronousServerSocketChannel的accept使用CompletionHandler异步发送、异步读取

ChatGpt对netty使用NIO的解读:
Netty使用的是NIO模型,但是它在NIO的基础上实现了异步非阻塞模型。具体来说,Netty使用了Java NIO提供的Selector机制,实现了事件驱动的异步非阻塞通信模型。在Netty中,所有的I/O操作都是异步的,不会阻塞线程。当有数据可读或可写时,Netty会通知应用程序进行相应的处理。因此,虽然底层使用的是NIO模型,但Netty本身实现了更高层次的异步非阻塞通信模型,使得开发者可以更加方便地编写高性能、可扩展的网络应用程序。

直接内存和非直接内存:
    直接内存为非堆内存,java中可以通过ByteBuffer.allocateDirect()申请,非直接内存为堆内存java中可以通过ByteBuffer.allocate()申请

堆内存创建快,读写慢。堆外内存,创建慢,读写快
    
java中io模型有:BIO、NIO、AIO
    BIO属于同步阻塞
    NIO属于I/O 多路复用模型(Buffer、Channel、Selector)与多路复用epoll
    AIO属于异步IO

多路复用器有哪些:select、poll、epoll等

DMA控制器:
    在进行I/O设备和内存的数据传输,数据的搬运工作都交由DMA控制器完成,而CPU不再参与任何与数据搬运有关的事情
    
Buffer:负责数据存储
    Buffer(缓冲区)是一个用于存储特定基本类型数据的容器。除了boolean外,其余每种基本类型都有一个对应的buffer类。Buffer类的子类有ByteBuffer, CharBuffer, DoubleBuffer, FloatBuffer, IntBuffer, LongBuffer, ShortBuffer 。
socket读写的原理/Buffer原理:
    队列queue,发送的队列和接收的队列
Channel:负责数据的传输,不负责存储,需要配合buffer使用
    Channel(通道)表示到实体,如硬件设备、文件、网络套接字或可以执行一个或多个不同 I/O 操作(如读取或写入)的程序组件的开放的连接。Channel接口的常用实现类有FileChannel(对应文件IO)、DatagramChannel(对应UDP)、
    SocketChannel和ServerSocketChannel(对应TCP的客户端和服务器端)。Channel和IO中的Stream(流)是差不多一个等级的。只不过Stream是单向的,譬如:InputStream, OutputStream.而Channel是双向的,既可以用来进行读操作,又可以用来进行写操作。
Selector
    Selector(选择器)用于监听多个通道的事件(比如:连接打开,数据到达)。因此,单个的线程可以监听多个数据通道。即用选择器,借助单一线程,就可对数量庞大的活动I/O通道实施监控和维护。
    linux epoll实现原理:
        Selector.open()-->SelectProvider.provider()--->DefaultselectorProvider.create()-->EpollSelectorProvider.openSelector()-->new EpollSelectorImpl()-->new EpollArrayWrapper-->epollCreate()得到epfd-->调用native的linux内核函数epoll_create实例
        ServerChannel.register--SelectorImpl.register--->pollArrayWrapper.add(fd)将fd注册到EpollArrayWrapper这个集合中
        selector.select-->EpollSelectorImpl.doSelect--pollArrayWrapper.poll-->epollCtl()进行事件绑定-->调用内核的epoll_ctl
                                                                              线程updateRegistrations-->调用epollWait-->调用linux内核的epoll_wait方法
    select实现原理:数组
        int select(int maxfdp, fd_set *readset, fd_set *writeset, fd_set *exceptset,struct timeval *timeout);
        int FD_ZERO(int fd, fd_set *fdset);     // 一个 fd_set 类型变量的所有位都设为 0
        int FD_CLR(int fd, fd_set *fdset);      // 清除某个位时可以使用
        int FD_SET(int fd, fd_set *fd_set);     // 设置变量的某个位置位
        int FD_ISSET(int fd, fd_set *fdset);    // 测试某个位是否被置位
        
    poll实现原理:
        int poll(struct pollfd *fds, nfds_t nfds, int timeout);
        struct pollfd {
            int fd;         // 文件描述符
            short events;   // 感兴趣的事件
            short revents;  // 实际发生的事件
        };
    poll、select、epoll区别:
                select--    poll            epoll
    操作方式    遍历        遍历            回调
    底层实现    数组        链表             红黑树
    io效率        On            On                 O1
    最大连接数  1024        无上限            无上限
    fd拷贝        每次调用select/poll,都需要    调用epoll_ctl拷贝进内核并保存,每次调用epoll_wait不拷贝
                把fd集合从用户态拷贝到内核
                

dubbo通信,server处理完结果后,将结果消息发送给client,client收到很多消息,怎么知道哪个消息结果是原先哪个线程调用的
    使用一个ID,让其唯一,放到map中,然后传递给服务端,ResponseFuture再服务端又回传回来,这样就知道结果是原先哪个线程的了

NIO和BIO比较
    相比于传统的 BIO 模型来说, NIO 模型的最大改进是:
    1.  使用比较少的线程便可以管理多个客户端的连接,提高了并发量并且减少的资源消耗(减少了线程的上下文切换的开销)
    2.  在没有 I/O 操作相关的事情的时候,线程可以被安排在其他任务上面,以让线程资源得到充分利用。
    使用 NIO 编写代码比较复杂
    
NIO中网络io中SocketChannelImpl读写数据流程:
    1:读
         从输入流读取数据,委托给IOUtil,从输入流读取数据写到ByteBuffer数组中,再将buffer添加到iovecwrapper的字节缓冲区数组,最后通过SocketDispatcher,从filedescriptor对应的输入流读取数据,写到iovecwrapper的缓冲区中
    2:写
        SocketChannelImpl写ByteBuffer数组方法,首先同步写锁,确保通道,输出流打开,连接建立委托给IOUtil,将ByteBuffer数组写到输出流中,这一过程为获取存放i个字节缓冲区的IOVecWrapper,最后通过SocketDispatcher,将iovecwrapper的缓冲区数据,写到filedescriptor对应的输出流中

访问一个URL经历了哪些过程    
    获取URL - > DNS解析 - > TCP连接 - >封装并发送报文 ->服务器接收并处理请求 - >返回报文 - >浏览器解析渲染页面 - > TCP断开连接
    
TCP结构:
    TCP的连接是需要四个要素确定唯一一个连接:
    (源IP,源端口号)+ (目地IP,目的端口号)
    所以TCP首部预留了两个16位作为端口号的存储,而IP地址由上一层IP协议负责传递
    源端口号和目地端口各占16位两个字节,也就是端口的范围是2^16=65535
    另外1024以下是系统保留的,从1024-65535是用户使用的端口范围

    TCP的序号和确认号:
    32位序号 seq:Sequence number 缩写seq ,TCP通信过程中某一个传输方向上的字节流的每个字节的序号,通过这个来确认发送的数据有序,比如现在序列号为1000,发送了1000,下一个序列号就是2000。
    32位确认号 ack:Acknowledge number 缩写ack,TCP对上一次seq序号做出的确认号,用来响应TCP报文段,给收到的TCP报文段的序号seq加1。

    TCP的标志位
    每个TCP段都有一个目的,这是借助于TCP标志位选项来确定的,允许发送方或接收方指定哪些标志应该被使用,以便段被另一端正确处理。
    用的最广泛的标志是 SYN,ACK 和 FIN,用于建立连接,确认成功的段传输,最后终止连接。

    SYN:简写为S,同步标志位,用于建立会话连接,同步序列号;
    ACK:简写为.,确认标志位,对已接收的数据包进行确认;
    FIN:简写为F,完成标志位,表示我已经没有数据要发送了,即将关闭连接;
    PSH:简写为P,推送标志位,表示该数据包被对方接收后应立即交给上层应用,而不在缓冲区排队;
    RST:简写为R,重置标志位,用于连接复位、拒绝错误和非法的数据包;
    URG:简写为U,紧急标志位,表示数据包的紧急指针域有效,用来保证连接不被阻断,并督促中间设备尽快处理;
    
    三次握手的状态:SYN_SENT、SYN_RCVD、ESTABLISHED

    
http三次握手四次挥手
三次握手(作用:确认发送成功)
    Client向服务端发送连接请求,进入SYN_SENT状态,SYN=1、seq=J
    Server收到客户端发送的请求并返回ACK确认是否可以连接请求,进入SYN_RCVD状态,SYN=1、ACK=1、seq=K、ack=J+1
    客户端收到确认请求,发送数据,进入ESTABLISHED监听状态,ACK=1、ack=K+1
    注:小写的ack代表的是头部的确认号Acknowledge number, 缩写ack,是对上一个包的序号进行确认的号,ack=seq+1。
        大写的ACK,则是我们上面说的TCP首部的标志位,用于标志的TCP包是否对上一个包进行了确认操作,如果确认了,则把ACK标志位设置成1
四次挥手(由于TCP 是全双工信道,即客户端连接)
    第一次挥手:
    Client发送一个FIN,用来关闭Client到Server的数据传送,Client进入FIN_WAIT_1状态。
    第二次挥手:
    Server收到FIN后,发送一个ACK给Client,确认序号为收到序号+1(与SYN相同,一个FIN占用一个序号),Server进入CLOSE_WAIT状态、Client进入Fin_WAIT_2状态。
    第三次挥手:
    Server发送一个FIN,用来关闭Server到Client的数据传送,Server进入LAST_ACK状态。
    第四次挥手:
    Client收到FIN后,Client进入TIME_WAIT状态,接着发送一个ACK给Server,确认序号为收到序号+1,Server进入CLOSED状态,完成四次挥手。
    
ssl四次握手:
    客户端请求建立SSL链接,并向服务端发送一个随机数–Client random和客户端支持的加密方法,比如RSA公钥加密,此时是明文传输。    对应https原理与交互过程1阶段
    服务端回复一种客户端支持的加密方法、一个随机数–Server random、授信的服务器证书和非对称加密的公钥。 对应https原理与交互过程2阶段
    客户端收到服务端的回复后利用服务端的公钥,加上新的随机数–Premaster secret 通过服务端下发的公钥及加密方法进行加密,发送给服务器。  对应https原理与交互过程3、4、5、6阶段
    服务端收到客户端的回复,利用已知的加解密方式进行解密,同时利用Client random、Server random和Premaster secret通过一定的算法生成HTTP链接数据传输的对称加密key–session key 对应https原理与交互过程5、6、7阶段
    
TCP如何保证数据的可靠传输(三次握手主要为了保证可靠性传输,通过ack和seq):
    1.tpc给每个包一个编号,这个编号是有序的,接收方有序的接收数据
    2.会进行流量控制
    3.会进行拥阻控制
    4.使用ARQ协议,一组发完了才能进行下一组发送
    5.会进行超时重传
    6.应用数据会分割成合适的块
    7.会丢弃重复数据
    8.会进行校验和

linux监听网络命令:tcpdump、netstat

在浏览器中输入 url 地址 ->> 显示主页的过程
    DNS 解析
    TCP 连接
    发送 HTTP 请求
    服务器处理请求并返回 HTTP 报文
    浏览器解析渲染页面
    连接结束

http的keepalive 和 tcp的keepalive
    http协议的keepalive什么意思呢?想和对方保持长连接,长连接就是咱们俩三次握手建立连接后一直用这个连接发送数据
    tcp的keepalive完全是另外一件事,主要作用是通过定时发送探测包来探测连接的对端是否存活的一种机制
    
健康检查应该用内核的keepalive还是应用层的http
    应该用应用层的http定时进行http检查,使用内核的ping的keepalive,只能判断当前连接的端口号或者线程是通的,但是整个应用是否通需要使用http确定
    
    
为什么要三次握手,为什么要四次挥手:
    建立可靠的通信信道(产生的序列号seq,保证服务端可以顺序接收),确认自己与对方的发送与接收是正常的
    四次挥手为了断开连接前的确认操作,发送连接释放的信号

为什么建立连接是三次握手,而关闭连接却是四次挥手呢
    四次挥手相对于三次握手中间的两个操作,ACk确认后再发送FIN包,在确认关闭之后服务端只是进入了close_wait状态仍能发送数据和待回收资源,两个步骤是分开的,而握手的时候只需要确认可以发送即可
    
什么是TCP可靠性传输:
    所谓可靠传输是指在通讯时常见的丢包,乱序的条件下依然可以保障数据被依序接受,中间不丢数据。是靠序列号,滑动收发窗口, 接收方ACK 和 SACK, 发送方重传等机制保障的

TCP分包和粘包问题:
    TCP每次数据发送都会以数据包的形式进行发送,而数据包发送是一段一段的进行发送,且是顺序发送,数据包格式中包含了数据头和数据信息,数据头信息中会存在长度信息,而服务器端只需要截取指定长度数据信息即可,然后获取该次数据的完整信息
    分包可以分成三种:1.定长传输 2.以分隔符传输 3分成头部和body传输,头部存储当前字节长度
    
TCP长连接(http1.1开始默认都是长连接)和短连接:
    理论:
        短连接: 每次发起HTTP请求都要进行连接,然后再传输完后关闭连接。
        长连接: 发起完HTTP请求后,并不关闭连接,当连接复用次数到一定次数,或者过了一定时间、或者客户端主动释放连接才会关闭连接。
    从服务端代码确认层面的处理:
        read调用的时候是否是循环read,对于短连接的时候,只需要read一次就可以了,对于长连接需要使用while循环不断read调用,直到客户端主动释放连接,服务端是不能主动释放连接的
    
https原理与交互过程(https://zhuanlan.zhihu.com/p/43789231):
    对于浏览器分为:1请求网络链接、3证书的合法性校验、4生成对称加密随机密钥R、5用证书里的非对称加密公钥加密、6传输使用公钥加密后的R、10使用R解密网页内容
    对于服务器:2确认加密算法和hash算法并返回证书、7用私钥解密R获取R、8使用R加密网页内容、9返回加密的网页内容

https交互过程涉及的名词和技术:
    对称加密、非对称加密、CA认证、数字签名、摘要(Hash算法、MD5、SHA1等)、ssl(安全套接层协议)/TlS(安全传输层协议)、
    
TLS使用的密码技术 
    伪随机数生成器(为了生成随机对称密钥):秘钥生成随机性,更难被猜测 
    对称密码(用于加密数据):对称密码使用的秘钥就是由伪随机数生成,相较于非对称密码,效率更高 
    消息认证码(为了确保消息的发送是真实的发送方):保证消息信息的完整性、以及验证消息信息的来源 
    公钥密码(用于非对称加密):证书技术使用的就是公钥密码 
    数字签名(用于验证服务器给的证书是真实的证书,而不是伪造的,使用散列的hash函数或者md5):验证证书的签名,确定由真实的某个 CA 颁发 
    证书(保存公钥和基本信息):解决公钥的真实归属问题,降低中间人攻击概率
    
TSL与ssl区别:
    SSL表示安全套接字层,而TLS表示传输层安全,tls使ssl增强版本,原理基本上一致,就是一些证书处理和伪随机数的生成改变了(ssl使用MAC,tls使用HMAC)

为什么要用对称加密+非对称加密
    为了兼顾安全与效率,对数据进行对称加密,对称加密所要使用的密钥通过非对称加密传输。
为什么不能只用非对称加密
    非对称加密耗性能,效率低,所以只做了一次非对称加密
为什么需要数字证书(CA机构)
    证明浏览器收到的公钥一定是该网站的公钥,证明身份使用,类似于公安局发的身份证
为什么需要数字签名
    为了数字证书的“防伪技术”,防篡改(CA机构是浏览器信任的机构,所以浏览器保有它的公钥)
浏览器如何保有CA机构的公钥
    操作系统、浏览器本身会预装一些它们信任的根证书,如果其中会有CA机构的根证书,这样就可以拿到它对应的可信公钥了
    
多线程产生的问题:死锁、线程不安全、内存泄漏

多线程的内存泄漏问题:
    AsyncTask和Runnable都使用了匿名内部类,那么它们将持有其所在Activity的隐式引用。如果任务在Activity销毁之前还未完成,那么将导致Activity的内存资源无法被回收,从而造成内存泄漏

死锁和死锁解决方案:
    互斥条件:
    请求与保持条件:一次性申请所有资源
    不剥夺条件:获取不到锁释放自己的锁
    循环条件:按顺序获取锁

内存泄漏:
    资源未关闭造成的内存泄漏
    单例周期长
    非静态内部类创建静态实例造成的内存泄漏
    线程造成的内存泄漏:AsyncTask和Runnable都使用了匿名内部类,使用静态内部类进行替换或者独立为一个类,也可以采用线程池替换,关闭使用shutdown方法
    集合容器中的内存泄露
    Handler造成的内存泄漏:也是持有Activity引用未释放,解决方式是实现Handler的接口静态化,Handler类中持有的对象弱引用化

如何保证线程安全:保证线程安全实际是保证程序的正确执行和共享数据的正确读写,实现方式如下
    线程同步:使用关键字synchronized、CAS机制、使用原子类、volatile禁止指令重排和保证可见性
    线程隔离:ThreadLocal

线程池的好处:
    降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。 
    提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。 
    提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控
    
线程的调度模型:
    分时调度模型(非抢占式调度模型):指让所有的线程轮流获得cpu的使用权,并且平均分配每个线程占用的CPU的时间片
            守护线程就是属于非抢占式调度模型,例子有,redission锁的看门狗模式,JVM的垃圾回收线程
    抢占式调度模型:优先让可运行池中优先级高的线程占用CPU
        调整各个线程的优先级方式有
            让处于运行状态的线程调用Thread.sleep()方法
            让处于运行状态的线程调用Thread.yield()方法
            让处于运行状态的线程调用另一个线程的join()方法,当前处于运行状态的线程阻塞等到另一个线程结束
            
sleep、wait、yield区别:
    sleep:sleep()用于休眠当前线程,sleep()不释放对象锁,让出CPU,线程状态为等待状态
    wait:wait()用于线程同步或者线程之间进行通信,wait()释放对象锁,让出CPU,线程状态为等待状态/超时等待
    yield:yield()临时暂停当前正在执行的线程,yield()仅释放线程所占用的CPU,线程状态为回到就绪状态    
    
容器
hashMap长度为什么是2的幂次方(new HahsMap若没指定大小默认大小在put第一个值的时候默认大小为16)
    为了尽可能的使数据在数组中分散均匀,减少hash碰撞并可以【提高运算效率】,hashMap采用了扰动函数(就是hashMap自己的hash方法)得到hash值,数据下标的位置运算为(n-1)& hash,其中n等于数组长度
例如:假设数组长度16,一个odj对象hashCode为x,则计算hash(obj)=(h = key.hashCode) ^ (h >>> 16)=x^(x>>>16)得到二进制的后四位,则数组的下标就为z=y % 16= y & (16-1),
要使得这个公式成立,则数组的大小必须是2幂次方
get方法如何确定key在数组中的位置:
    先通过 hash(key) 得到对应的hash值,再通过 tab[(n-1) & hash] 来确定位置。hash(key)为(h = key.hashCode) ^ (h >>> 16)
hash(key)函数为(h = key.hashCode) ^ (h >>> 16)这个公式的作用
    混合高位和地位来加大随机性
总结:通过异或移位运算加大hash值得随机性,通过&位运算提供运算效率,而如果需要用位运算则数组的长度必须是2的幂次方
new HashMap<>()的时候有没有指定容量
    new 一个hashmap时并没有指定容量,只是计算出了loadfactor,在真正第一次put值的时候才会初始化容量,如果new HashMap<>()则默认为是16,如果指定了容量,则默认指定容量上限的2的幂次方倍,如new HashMap<>(7),则put第一个值时初始化容量为8,
    当put到第7个值的时候++size > threshold会扩容到16,记住:第六个的时候并不会产生扩容

hashMap的头插法和多线程下的死循环:
    关于头插法和尾插法:
        jdk1.8尾插法出现在新增元素冲突的时候加入到链表的尾部,jdk1.7头插法指新增元素冲突的时候加入到链表的头部,扩容的时候会出现死循环的情况
    死循环问题
        jdk1.7和1.8都会出现死循环的情况
        jdk1.7在扩容时出现死循环
        jdk1.8链表转换为树,对树进行操作时

什么是AQS及其原理:
    AQS为构建锁和同步器的框架,提供一套线程阻塞和唤醒的机制,采用双向队列,将暂时获取不到锁的线程放入队列中进行等待
    
CountDownLatch订单模块应用(mysql数据通过canal同步es)
    canal订阅订单(更新插入删除log),提取log日志,解析log日志,循环每次取1000条数据,根据hash算法以订单维度放到不同的队列中,队列数是指定的,
    并分多个线程执行任务队列中的数据,此时需要使用CountDownLatch,,标记同步es成功与失败的记录,当所有组线程都执行完毕之后,数据处理异常数量统计(进行canal回滚及响应操作)
    
定时提醒延时跑批业务(订单还车前延迟消息、订单排车延迟消息、畅游租订单取车延迟消息、畅游租订单还车延迟)
    1.在特定节点,通过计算当前时间是否能够落到rocketmq的18个延时级别上,如果落不到对应的mq级别,则通过redis,计算分数为通知时间与当天0点的毫秒差值,addZSet存入zset
    2.通过订单定时提醒跑批,创建不同的延时业务task,订单还车前延时消息和订单排车延时消息分成不同的两个task,task实现了Runnable
    3.设置CountDownLatch,跑批是10分钟执行一次,设置CountDownLatch最长阻塞时间为10分钟。
    4.调用线程池的submit方法,
    5.task任务的处理,rangeByScore计算当次跑批拉取zset数据的score范围(跑批每10分钟执行一次,将当前时间往后推迟10分钟,计算10分钟内需要发送的消息)
    6.判断当前消息是否落入rocketmq的延时级别,如果落入mq延时级别,放入到mq的延时级别,否则不做处理,当放入到rocketmq延时级别之后,zerm删除redis中的缓存数据

mysql存储过程应用(mysql分表通过canal同步整合到其他数据库中)
    canal通过解析分表数据,解析所有分表数据将分表后缀剔除,执行插入数据的存储过程到对应的整合数据库中
    
对象实例只能分配在堆上吗:不是
    对象实例如果未发生了逃逸会在栈上进行分配

JIT优化技术:方法内联和逃逸分析
    方法内联:把「目标方法」的代码复制到「调用的方法」中,避免发生真实的方法调用,因为每次方法调用都会生成栈帧(压栈出栈记录方法调用位置等等)会带来一定的性能损耗,所以「方法内联」的优化可以提高一定的性能
    逃逸分析:判断一个对象是否被外部方法引用或外部线程访问的分析技术,如果「没有被引用」,就可以对其进行优化,
        1. 锁消除(同步忽略):该对象只在方法内部被访问,不会被别的地方引用,那么就一定是线程安全的,可以把锁相关的代码给忽略掉
        2. 栈上分配:该对象只会在方法内部被访问,直接将对象分配在「栈」中(Java默认是将对象分配在「堆」中,是需要通过JVM垃圾回收期进行回收,需要损耗一定的性能,而栈内分配则快很多)
        3. 标量替换/分离对象:当程序真正执行的时候可以不创建这个对象,而直接创建它的成员变量来代替。将对象拆分后,可以分配对象的成员变量在栈或寄存器上,原本的对象就无需分配内存空间了
    
什么是逃逸分析,举个例子
    某些方法中的对象引用没有被返回或者未被外面使用(也就是未逃逸出去),则叫未逃逸,否则为逃逸,未逃逸的情况会在栈上分配对象,比如以下两种情况:
        一、对象被赋值给堆中对象的字段和类的静态变量、
        二、对象被传进了不确定的代码中去运行
        public class EscapeTest {

            public static Object globalVariableObject;

            public Object instanceObject;

            public void globalVariableEscape(){
                globalVariableObject = new Object(); //静态变量,外部线程可见,发生逃逸
            }

            public void instanceObjectEscape(){
                instanceObject = new Object(); //赋值给堆中实例字段,外部线程可见,发生逃逸
            }
            
            public Object returnObjectEscape(){
                return new Object();  //返回实例,外部线程可见,发生逃逸
            }

            public void noEscape(){
                synchronized (new Object()){
                    //仅创建线程可见,对象无逃逸
                }
                Object noEscape = new Object();  //仅创建线程可见,对象无逃逸
            }

        }

开启逃逸分析:-XX:+DoEscapeAnalysis
逃逸分析好处:
    方法栈上的对象在方法执行完之后,栈桢弹出,对象就会自动回收。这样的话就不需要等内存满时再触发内存回收。这样的好处是程序内存回收效率高,并且GC频率也会减少,程序的性能就提高了

逃逸分析是方法级别的,因为JIT的即时编译是方法级别

什么代码是JIT编译的代码:(热点代码)(热点代码阈值:-XX:CompileThreshold)
    1、被多次调用的方法
    2、被多次执行的循环体
    
堆栈方法区分配
    public class Fruit {
        final static int constantPrice = 50;    //50分配在常量池 ,constantPrice分配在方法区
        final static String constantName = "水果"; //水果分配在常量池 ,constantName分配在方法区
     
        static int staticPrice = 100; //100分配在常量池 ,staticPrice分配在方法区
        static BigWaterMelon thin = new BigWaterMelon("皮薄", staticPrice);  //thin分配引用在方法区,实例分配在堆 ,"皮薄"分配在常量池 
     
        int instancePrice = 200;  //instancePrice引用在方法区,实例分配在堆 ,实例是200 
        BigWaterMelon juicy = new BigWaterMelon("多汁", instancePrice); //juicy分配引用在方法区,实例分配在堆 ,"多汁"分配在常量池 
     
        public static void main(String[] args) {
            Fruit fruit = new Fruit();  //fruit实例分配在堆 ,实例是fruit 
            int mainThreadLocalPrice = 300; //mainThreadLocalPrice实例分配在堆 ,实例是300
    }
整体方法调用过程:虚拟机栈出栈和入栈    
jit方法调用计数器的过程
    1.方法入口 2.判断是否存在编译的版本
    3.存在则执行编译后的本地代码版本
    4.不存在则
        4.1方法调用计数器+1
        4.2判断是否到达阈值
        4.3向jit编译器提交编译请求
        4.4以解释方式执行方法
    5.方法返回
    
什么是热点代码(判断是否达到阈值调用次数)
    (2)阈值设置
       可以通过"-XX:CompileThreshold"参数设定;
·    (3)阈值表示
        (A)执行频率
            方法调用执行频率:一段时间内方法被调用的次数;
           可以通过"-XX:CounterHalfLifeTime"参数
        (B)方法调用的绝对次数
           可以通过"-XX:-UseCounterDecay"参数

CPU100%问题(跑批迁移欠款数据问题)
    ps -ef|grep java找到java应用的进程
    top -Hp pid找出占用CPU的当前进程的pid
    echo "obase=16;2944" | bc输出pid2944表示16进制字符
    jstack pid > pid.dump 线程栈导出到文件
    
内存泄漏问题排查经验:
    top下对当前服务器内存有个大致了解
    ps找到对应的进程id,并查看到对应的启动jvm信息
    jstat -gcutil  17561  1000 10查看gc运行状态详细信息
    分析找出内存占用超出预期的嫌疑对象:
        方法1:下载dump文件使用MAT工具进行分析
        方法2:堆Dump在线可视化分析: https://heaphero.io/
        方法3:使用jhat命令获取分析报告,并在浏览器查看分析报告
    
OOM内存溢出问题(数据查询过多问题)
    top下对当前服务器内存有个大致了解
    ps -ef|grep java找到java进程
    jmap -dump:format=b,file=heap.prof  17561生成dump文件
    使用jhat命令获取分析报告或利用MAT分析dump文件(也可以用jdk自带的jvisualvm.exe)
    选择 “list Object”、“with incoming references”

2PC
1、同步阻塞问题(不在本操作中的其他事务操作共享资源时会被阻塞)。

2、协调者挂机导致参与者阻塞。(参与者资源正在锁住资源,协调者挂机阻塞参与者)

3、数据不一致。在二阶段提交的阶段二中,当协调者向参与者发送commit请求之后,发生了局部网络异常或者在发送commit请求过程中协调者发生了故障,这回导致只有一部分参与者接受到了commit请求。
   而在这部分参与者接到commit请求之后就会执行commit操作。但是其他部分未接到commit请求的机器则无法执行事务提交。于是整个分布式系统便出现了数据部一致性的现象

3PC可以解决同步阻塞问题和协调者挂机导致参与则阻塞(CanCommit、PreCommit、DoCommit三个阶段)
    1、引入超时机制。同时在协调者和参与者中都引入超时机制。
    2、在第一阶段和第二阶段中插入一个准备阶段。保证了在最后提交阶段之前各参与节点的状态是一致的。
    PreCommit阶段将undo和redo信息记录到事务日志中,对资源进行加锁(资源还未释放)
    在协调者挂机之后,如果已经到达了PreCommit阶段且是yes,就算协调者挂机了收不到DoCommit也会默认执行事务并释放资源

KAFKA和rocketmq的区别
    kafka不支持严格顺序消费,Broker宕机后,就会产生消息乱序
    kafka不支持定时消息
    kafka不支持同步刷盘,数据不可靠
    kafka不支持tag过滤,但是支持partition
    kafka不支持消息重试
    kafka不支持事务,rocketMq可以做分布式事务
    kafka单机写入速度更快(sendfile)
    kafka在日志领域更成熟,rocketmq在业务应用领域更成熟    
    
ThreadLocal解决hash冲突的方法
    使用黄金分割数减少hash冲突,并通过开放地址法解决hash冲突

ThreadLocal原理
    使用threadLocalMap存储局部变量数据,key存储的是每个ThreadLocal,value存储数据,key为弱引用,会产生垃圾,在通过set、get、remove过程会把key为null的数据清理
    
threadLocalMap数据结构
    采用数组存储数据
清理过程
    在set操作下,如果遇到key过期的数据(key为null,entry不为null),先进行探测式清理(在当前key为null往前查找key也为null的区间清理) 例如: a knull b c null knull c d e knull会找到knull c d e knull这个区间
    expungeStaleEntry 探测式清理(分段清理)
    cleanSomeSlots启发式清理(启发式清理包含了expungeStaleEntry,真正做清理的还是在expungeStaleEntry里)
ThreadLocal重要方法:
    get、set、getMap、createMap、replaceStaleEntry、cleanSomeSlots、expungeStaleEntry、nextIndex、rehash、prevIndex
重要参数:
    threshold、size、sz、HASH_INCREMENT、table
    
synchronized的底层Monitor实现:
    ObjectMonitor() {
        _count        = 0; //用来记录该对象被线程获取锁的次数
        _waiters      = 0;
        _recursions   = 0; //锁的重入次数
        _owner        = NULL; //指向持有ObjectMonitor对象的线程 
        _WaitSet      = NULL; //处于wait状态的线程,会被加入到_WaitSet
        _WaitSetLock  = 0 ;
        _EntryList    = NULL ; //处于等待锁block状态的线程,会被加入到该列表
    }

synchronized锁升级:对象刚开始建立时,属于无锁状态,当第一个线程进来时,如果偏向锁开启(-XX:+UseBiasedLocking),进行偏向锁加锁,当第二个线程进来时,此时锁的状态是偏向锁,此时需要将锁偏向撤销,
升级为轻量级锁,进行CAS锁竞争,未获取到锁的时候,膨胀为重量级锁,当持有锁的线程释放锁后,唤醒堵塞的线程, 然后线程再次竞争锁,但此时锁为重量级锁,不会
退化为轻量级锁,当线程执行完毕,即线程不存活的时候,重置锁状态为无锁
重量级锁 对应 10
轻量级锁 对应 00
无锁和偏向锁都对应01,这个时候需要倒数第三个字节加以区分
即 无锁 对应 001, 偏向锁 对应 101

偏向锁的撤销(线程B获取到了偏向锁,此时线程A进来)
    偏向锁的撤销需要等待全局安全点(safe point,代表了一个状态,在该状态下所有线程都是暂停的,stop-the-world),到达全局安全点后,持有偏向锁的线程B也被暂停了。
    检查持有偏向锁的线程B的状态(会遍历当前JVM的所有线程,如果能找到线程B,则说明偏向的线程B还存活着):
        5.1 如果线程还存活,则检查线程是否还在执行同步代码块中的代码:
          5.1.1 如果是,则把该偏向锁升级为轻量级锁,且原持有偏向锁的线程B继续获得该轻量级锁。
        5.2 如果线程未存活,或线程未在执行同步代码块中的代码,则进行校验是否允许重偏向:
          5.2.1 如果不允许重偏向,则将Mark Word设置为无锁状态(未锁定不可偏向状态),然后升级为轻量级锁,进行CAS竞争锁。
          5.2.2 如果允许重偏向,设置为匿名偏向锁状态(即线程B释放偏向锁)。当唤醒线程后,进行CAS将偏向锁重新指向线程A(在对象头和线程栈帧的锁记录中存储当前线程ID)。
    唤醒暂停的线程,从安全点继续执行代码

synchronized和volatile有序性的区别
    synchronized的有序性是持有相同锁的两个同步块只能串行的进入,即被加锁的内容要按照顺序被多个线程执行,但是其内部的同步代码还是会发生重排序,使块与块之间有序可见。
    volatile的有序性是通过插入内存屏障来保证指令按照顺序执行。不会存在后面的指令跑到前面的指令之前来执行。是保证编译器优化的时候不会让指令乱序。
    通俗讲:synchronized保证了同步代码的有序性,volatile保证了指令的有序性
    synchronized 是不能保证指令重排的。
    synchronized可以保证原子性和可见性

concurrenthashmap
    1.7采用的是sengment分段数组,默认为2,默认初始化16个并发级别,所以最大并发数是16,也可以指定,但是一旦指定之后就不可以改变
        首先第一个线程进来的时候,先去获取对应的sengment分段数组,如果分段数组为null,则需要初始化分段数组,初始化对应hashEntry数组,产生线程竞争的时候采用cas进行竞争
    1.8采用的是Node数组+链表+红黑树的结构
        首先根据key计算出hashCode,
        判断是否需要进行初始化
        如果定位到的node为空则利用CAS写入,写入的时候判断是否需要进行扩容
        如果位置不为空,判断是否正在扩容,正在扩容就帮助完成扩容,否则所有条件不满足使用synchronized锁写入数据,如果当前写入的链表数量大于阈值,则转化为红黑树

锁粗化:多个锁合并为一个锁
锁消除:通过JIT编译,经过逃逸分析,去除不可能存在锁竞争的锁
    
内存分配如何保证线程安全:
    虚拟机采用两种方式来保证线程安全:CAS+失败重试、TLAB分配对象
    
一个对象的结构
    MarkWord 8、ClassPoint类型指针4/8、实例数据、padding填充
    
一个【Mark word结构】对象头的结构(64位vm)
    对于无锁状态
    25位unused+31位hashcode+1unused+4分代年龄+1偏向锁+2锁标志位
    对于偏向锁
    54线程指针+2Epoch+1unused+4分代年龄+1偏向锁+2锁标志位
    对于轻量级锁
    62位指向线程栈中lockRecord的指针+2锁标志位
    对于重量级锁
    62位指向互斥量重量级锁monitor的指针+2锁标志位
    注:分代年龄无论多少位的hotspot锁状态都是4
    
一个【Mark word结构】对象头的结构(32位vm)
    无锁:25bit的hashCode+4bit的分代年龄+1bit值为0的偏向锁状态+2bit值为01锁标志位
    偏向锁:23bit的线程id+2bitEpoch+4bit的分代年龄+1bit值为1的偏向锁状态+2bit值为01锁标志位
    轻量级锁:30bit指向栈中锁记录的指针+2bit值为00锁标志位
    重量级锁:30bit指向重量级锁记录的指针+2bit值为10锁标志位
    自适应自旋锁:在 Java 6 之后自旋锁是自适应的,比如对象刚刚的一次自旋操作成功过,那么认为这次自旋成功的可能性会高,就多自旋几次;反之,就少自旋甚至不自旋,jdk1.7之后后无法设置,默认就是自适应

Mark Word重量级锁没有分代年龄是因为它是一种基本的同步机制,不需要考虑对象的年龄和GC过程。它仅仅是一个二进制标志,用于控制并发访问共享资源的互斥性。因此,它不需要考虑对象的生命周期和垃圾回收的影响。
    
CompressOops压缩指针
    OOP = “ordinary object pointer” 普通对象指针。 启用CompressOops后,会压缩的对象

为什么使用压缩指针
    32位的对象引用(指针)占4个字节,而64位的对象引用占8个字节。也就是说,64位的对象引用大小是32位的2倍。64位JVM在支持更大堆的同时,由于对象引用变大却带来了性能问题,如增加了GC开销、降低CPU缓存命中率

CompressedOops的原理
    32位内最多可以表示4GB(2^32),64位地址分为堆的基地址+偏移量,当堆内存<32GB时候,在压缩过程中,把偏移量/8后保存到32位地址。在解压再把32位地址放大8倍,所以启用CompressedOops的条件是堆内存要在4GB*8=32GB以内
    
一个简单的只有一个int属性的object对象的大小
    在未开启UseCompressedOops压缩指针是(MarkWord8+对象指针8+int数据4+对齐填充4=24字节)
    在开启UseCompressedOops压缩指针是(MarkWord8+对象指针4(被压缩了4)+int数据4=16字节)
一个简单的没有属性的object对象的大小
    在未开启UseCompressedOops压缩指针是(MarkWord8+类型指针8+实例数据0+对齐填充0=16字节)
    在开启UseCompressedOops压缩指针是(MarkWord8+类型指针4(被压缩了4)+实例数据0+对齐填充4=16字节)
    
哪些对象不会被压缩:
    指向PermGen的Class对象指针,本地变量,堆栈元素,入参,返回值,NULL指针不会被压缩

常量折叠(只有编译器在程序编译期就可以确定值的常量)
    String str3 = "str" + "ing" //这种情况会进行常量折叠
    String a="str";
    String b="ing";
    String c=a+b;  //不会进行常量折叠,会生成一个新的对象
注:尽量避免通过 new 的方式创建字符串,这样会生成一个新的对象

内存分配担保机制(简述当Eden分配不下,S区也无法进行分配的时候,当老年代空间足够就会把eden区对象分配到老年代)
    当新分配的对象(对象未达到大对象阈值,因为到达阈值会直接进入老年代)在eden区分配不下时,且eden区中的对象无法分配到S0区时,判断eden区总现存对象大小是否大于老年代剩余可用空间,如果不大于先进行minorGc,否则进行fullGc(minorGC+majorGc),当执行完Gc操作新分配的
    对象仍然无法在eden区分配时,且eden区现存对象小于老年代,则把eden中的对象放到老年代,此时将新对象放入老年代,如果仍然放不下,则抛出oom异常

无用的常量和无用类判断
    无引用的常量为无用常量
    无对象实例的类且被classLoader,对应的class对象被回收,再没有反射调用的类为无用的类,无用的类可以回收,但不是像对象一样必然回收
        
触发fullgc条件
    堆老年代内存不足
    方法区内存不足
    通过Minor GC后进入老年代的平均大小大于老年代的可用内存
    System.gc()方法的调用
    concurrent mode failure(无法处理“浮动垃圾”出现,老年代的垃圾收集器从CMS退化为Serial Old,所有应用线程被暂停,停顿时间变长)

CMS优点:并发收集、低停顿
cms的缺点
    对CPU资源敏感;
    无法处理浮动垃圾;
    它使用的回收算法-“标记-清除”算法会导致收集结束时会有大量空间碎片产生
CMS:
    初始标记:STW、耗时短、仅标记GCRoot能直接关联到的对象
    并发标记:不会STW,耗时长、从初始标记中的对象遍历整个对象图
    重新标记:重新标记、耗时短、修正并发标记期间用户线程导致的标记变动记录,相对初始标记要长
    并发清理:不会STW,产生浮动垃圾,并发失败,启用Serial old整理内存,停顿时间更长

标记流程如下:
    刚开始,所有的对象都是白色,没有被访问。
    将GC Roots直接关联的对象置为灰色。
    遍历灰色对象的所有引用,遍历后灰色对象本身置为黑色,引用置为灰色。
    重复步骤3,直到没有灰色对象为止。
    结束时,黑色对象存活,白色对象回收。
    
三色标记:
    黑色:对象本身及其所有的引用都被扫描过,黑色不能直接指向白色,不能被回收
    灰色:对象本身被扫描,还存在至少一个引用没有被扫描
    白色:没有被GC扫描,扫描结束后白色的可以被回收
    总结:黑色对象存活、白色对象回收
    漏标和错标:通过写屏障技术将用户线程引用关系记录下来,在重新标记的时候修正对象引用
        漏标:漏标的意思就是存活的对象没有被标记,例子: A(黑)-->B(灰)---C(白),经过并发标记之后,A-->C,B-->null,此时C重新被C引用了,但是黑色是被扫描过的,在重新标记时不会被扫描
        错标:错标指的是死掉的对象被标记了,例子:A(黑)-->B(灰)---C(白),GC线程已经遍历到B,此时刚好引用发生改变,A-->C,B-->null
        下次处理场景:
            漏标:该次重新标记处理
            错标:下次垃圾回收处理
        漏标通过写屏障解决
        错标通过写屏障和增量更新解决
    写屏障:类似于AOP加属性处理
    增量更新:黑色对象增加了对白色对象的引用之后,将它的这个引用,记录下来,在最后标记的时候,再以这个黑色对象为根,对它的引用进行重新扫描
G1特点:
    并行与并发、分代收集、空间整合、可预测的停顿时间(维护一个region优先列表,在可预测的停顿时间内回收n个region区域)
    
ldy jvm参数设置:
    -XX:+UseParNewGC  新生代使用Parallel收集器
    -XX:+UseConcMarkSweepGC 老年代回收期
     XX:+CMSParallelRemarkEnabled  启用并行标记,降低标记停顿
    -XX:NewRatio=3  新生代和老年代占比 1:3
    -XX:CMSInitiatingOccupancyFraction=60  当老年代达到60%时,触发CMS垃圾回收
    -XX:MaxTenuringThreshold=8 新生代GC晋升年龄
    -XX:SoftRefLRUPolicyMSPerMB=1  表示每一MB的空闲内存空间可以允许软引用对象存活多久 
    -XX:+PrintGCDetails  输出GC详细日志
    -XX:+PrintGCTimeStamps 输出GC的时间戳(以基准时间的形式)
    -XX:+PrintGCDateStamps  输出GC的时间戳(以日期的形式,如 2013-05-04T21:53:59.234+0800)
    -XX:+HeapDumpOnOutOfMemoryError  表示当JVM发生OOM时,自动生成DUMP文件
    -XX:HeapDumpPath=xxx 表示生成DUMP文件的路径,也可以指定文件名称,如果不指定文件名
    -Xloggc:xxx 指定GC日志文件的输出路径
    -XX:ErrorFile=xxx 当由于目标应用程序中的错误而发生崩溃时
    -Xms4G  初始化堆内存大小
    -Xmx4G  堆内存最大值
    -XX:MetaspaceSize=256M  MetaspaceSize容量触发FGC的阈值,超过设定阈值后MetaspaceSize每扩容一次触发一次FGC
    -XX:MaxMetaspaceSize=512M  用于设置metaspace区域的最大值,如果这个参数没有设置,那么就是通过mxbean拿到的最大值是-1,表示无穷大
    -XX:NewSize=2G 新生代初始内存的大小 ,同时指定了NewRatio和NewSize,MaxNewSize,会分配NewRatio的比例给新生代
    -XX:MaxNewSize=2G  新生代可被分配的内存的最大上限
    -Drocketmq.client.logRoot=xxx 启动路径
    
JVM调优:
    一般调优JVM可以参考:『吞吐量』、『停顿时间』和『垃圾回收频率』
    1. 内存区域大小以及相关策略 
    2.IO密集型的可以稍微把「年轻代」空间加大些,因为大多数对象都是在年轻代就会灭亡。内存计算密集型的可以稍微把「老年代」空间加大些,对象存活时间会更长些
    3.选择合适的垃圾回收器,以及各个垃圾回收器的各种调优参数
    4.通过诊断命令和工具监控(jps、jstat、jmap)
    5.最大堆和最小堆的大小一致,避免出现内存重新申请(malloc)

CPU 缓存模型(CUP--CPU缓存--主存RAM)
    CPU Cache 缓存的是内存数据用于解决CPU处理速度和内存不匹配的问题,内存缓存的是硬盘数据用于解决硬盘访问速度过慢的问题。CPU 解决内存缓存不一致性问题可以通过“总线加锁”(已淘汰)或制定缓存一致协议或者其他手段来解决
    
CPU多级缓存一致性协议MESI
    说白点:通过改变M、E、S、I不同状态解决
    1. 如果是读操作,不做任何处理,只是从缓存中读取数据到寄存器
    2. 如果是写操作,发出信号通知其它CPU将该变量的cache line置为无效状态,其它CPU在运行该变量读取的时候需要从主存更新数据。

JVM内存模型和java内存模型不是一个概念
    jvm内存模型是用作内存管理,jmm内存模型抽象了线程和主内存之间的关系、主要目的是为了屏蔽系统和硬件的差异,避免一套代码在不同的平台下产生的效果不一致
    JMM内存划分:主内存、工作内存,主内存对应jvm内存模型中的堆,是物理内存,工作内存对应的栈中的部分区域,是寄存器和高速缓存
    
JMM内存模型(线程--本地内存[寄存器]--主存)
    定义了JVM如何与计算机的主存进行工作,JMM中,线程的工作内存(或叫做本地内存)指的就是 CPU Cache,所以有缓存一致性的问题,线程可以把变量保存本地内存(比如机器的寄存器)中,而不是直接在主存中进行读写
    
JMM缓存一致性如何解决:
    把变量声明为volatile,这就指示 JVM,这个变量是共享且不稳定的,每次使用它都到主存中进行读取
    
关于volatile无法保证原子性问题:
    如下代码:
        public class UnsafeCounter {
            private volatile long count;
            private final long MAX = 10000;

            public void counter() {
                long start = 0;
                while (start++ < MAX) {
                    count++;
                }
            }
            public long getCount() {
                return count;
            }
        }
    两个线程在执行counter()的时候,count++会分成三步执行,读取--计算--赋值,线程1读取count=0,切换到线程2,count=0,计算count=1,赋值回主存,count=1,再切换回线程1,计算count=1,赋值回主存,此时,getCount的时候会发现count=1,而不是2

Java并发编程安全需要具备的三大特性:原子性、可见性和有序性。

jvm执行一个方法的流程
    创建栈帧
    保存main方法的程序计数器
    修改线程的局部变量表开始指针指向待执行的方法
    修改线程的操作数栈开始指针指向待执行方法
    方法执行完后,修改线程的操作数栈开始指针指向虚拟机栈前一个方法,如果没有就是main方法
    修改线程的局部变量表开始指针指向虚拟机栈前一个方法,如果没有就是main方法
    恢复main方法的程序计数器
    判断是否有返回值,有,就压入main方法的操作数栈
    释放栈帧
    
java栈中什么是动态链接,动态链接的作用:
    动态链接就是一个指向运行时常量池中栈帧所属方法的引用,作用为了将这些符号引用转换为调用方法的直接引用

什么是安全点safepoint
    (安全点是指线程被暂停时,被暂停的这行字节码不会导致引用关系发生变化)安全点是jvm选来进行GC的线程中断点。线程在执行到安全点后询问GC标志位,若标志位标识将要进行GC,则程序主动中断(非抢占式,抢占式中断需要低优先级进程把cpu资源给到高优先级)挂起线程等待GC。
    (通俗讲:jvm gc线程中断的时候,需要找到一个安全的点进行线程中断,询问GC标志位是否可以进行GC,要不然可能会导致引用关系改变)
    安全点的选定基本上是根据"是否具有让程序长时间执行的特征"为标准进行选定的。目前会产生安全点的主要有:1.方法调用、2.循环跳转、3.异常跳转。
什么是安全区域(可以看做安全点的拉伸)
    安全区域指,如果用户线程在GC线程STW主动中断前进入了sleep或者block状态,没有运行也就进入不了安全点。那么GC就无法开始STW,因为引用关系有可能发生变化。所以要引入安全区域,指能够确保在某一段代码片段中,
    引用关系都是不会发生变化的。在这个区域中任意地方进行垃圾回收都是安全的。可以看做安全点的拉伸。线程要离开安全区域,要看GC是否完成了STW,没有就要等待STW完成才能离开安全区域
    
方法递归调用层数过多会可能会栈溢出,怎么优化?
    可以用循环替代递归。减少方法的压栈

目前会产生安全点的主要有:1.方法调用、2.循环跳转、3.异常跳转。

mysql innodb和myIsam区别
    myIsam不支持事务
    myIsam不支持外键
    myIsam不支持行级锁
    myIsam不支持奔溃后恢复
    myIsam不支持mvcc
    
mysql锁:
    表锁、行锁、间隙锁gap lock(RC隔离级别不存在间隙锁)、临键锁next-key、MDL写锁(X锁)、MDL读锁(S锁)
    分类:共享锁(S)、排他锁(X)
    共享锁:读锁
        select *.....lock in share mode;
        一个事务开启了共享锁,其他事务只能进行读,不能进行CUD
    排他锁:写锁
        select *.....for update; / update xxxx;
        当前事务开启了排他锁,其他事务都不能获取到锁
    意向共享锁(IS):
        在获取共享锁之前需要获取意向共享锁,数据库用来自动优化共享锁的一个锁
    意向排他锁(IX):
        在获取排他锁之前需要获取意向排他锁,数据库用来自动优化排他锁的一个锁
    自增锁:
        自增主键的一个锁,对于没有提交的记录,id已经生成了,那么就算事务回滚,id就会丢失掉当前生成的id序列,
        如:插入id前id序列为1,2,3,4,此时插入5 ,6,如果5,6回滚了,此时继续自增会直接跳过5,6,比如来个7,那么现在序列就是1,2,3,4,7
    记录锁:
        行锁
    间隙锁gap lock(RC隔离级别不存在间隙锁,对于更新一个间隙的操作,rc隔离级别只会存在行锁锁住区间中的每一行)
        对一部分加锁,锁定一个范围,但不包含记录本身,一个间隙,(> < between),
        间隙锁锁定范围分析:
            对于聚簇索引:
                对于指定查询某一条记录的加锁语句,如果该记录不存在,会产生记录锁和间隙锁,如果记录存在,则只会产生记录锁,如:WHERE `id` = 5 FOR UPDATE;
                对于查找某一范围内的查询语句,会产生间隙锁,如:WHERE `id` BETWEEN 5 AND 7 FOR UPDATE;
                例子:主键记录有1,5,7,11, 产生的间隙是:(-infinity, 1]、(1, 5]、(5, 7]、(7, 11]、(11, +infinity],between<5,7>,则锁定的是(5, 11] 注意:mysql版本是5.6和5.7是锁定(5, 11],如果是高版本8.0+,只会锁定(5, 7]
            对于非聚簇索引:
                在普通索引列上,不管是何种查询,只要加锁,都会产生间隙锁,这跟唯一索引不一样;
                在普通索引跟唯一索引中,数据间隙的分析,数据行是优先根据普通索引排序,再根据唯一索引排序
                例子,数据结构为,现有记录(1, 1)、 (5, 3)、(7, 8)、(11, 12),此时:WHERE `number` = 3 FOR UPDATE,会产生间隙锁,number锁定(1, 8),id也会被锁定(1,7)
    临键锁next-key
        行锁+临键 (> < between) 锁定一个范围,并且锁定记录本身,左开,右闭 ,如果没有命中锁定记录,降级为间隙锁
    锁与隔离级别关系:
        幻读:临键锁解决
        不可重复读:S锁解决
        脏读:X锁解决
    
行锁原理:
    行锁锁的是索引上的索引项加锁,故而在RR隔离级别下,条件列未命中索引会锁表!而在RC隔离级别下,只锁行,通俗讲,如果你没有索引或没命中索引,那么就锁表了
    
mysql四个特性及解决实现原理
    原子性(Atomicity):利用undolog进行数据回滚
    一致性(Consistency):通过原子性、隔离性、持久性共同保证一致性,不同库的数据一致性还需要用到binlog保证
    隔离性(Isolation):锁机制、MVCC 等手段来保证事务的隔离性,SQL标准隔离级别有:ru、rc、rr、序列化,mysql默认使用rr
    持久性(Durability):利用redolog将数据刷盘到磁盘

并发事务出现的问题(不指定某种数据库,可以是mysql、oracle)
    脏读:当前事务看到其他事务未提交的事务,事务中总是能查到别人事务未提交的数据
    不可重复读:发生在update操作上,事务中两次查询结果不一致
    幻读:发生在insert和delete操作上,事务中两次查询结果不一致,比如select count(1) from table,事务1 第一次查询为5条,第二次查询和第一次查询中间事务2插入了1条数据,第二次查询得到6条,则为幻读
    
mysql存储方式,以数据页方式存放:SHOW TABLE STATUS

InnoDB 储存引擎支持有四种行储存格式:COMPACT、Redundant、Dynamic 和 COMPRESSED。默认为COMPACT

mysql在rr隔离级别下如何解决幻读:
    在快照读读情况下,mysql通过mvcc来避免幻读。
    在当前读读情况下,mysql通过next-key加锁来避免幻读

mysql分布式事务,必须使用SERIALIZABLE隔离级别
    原因是因为innodb支持XA事务,而分布式事务有是多个独立的事务参与到一个全局事务中,要么所有事务都执行,要么都不执行
    
现实中有没有遇到锁未释放的情况
    订单模块在结算欠款的时候需要更新收款记录和更新欠款表,订单有个逻辑,写了个死循环,导致长事务一直未释放,有个跑批需要更新收款记录信息,导致了该条记录一直未获取到锁,此时出现的是行级锁
    
红黑树特点:
    1.每个节点只能是红色或者黑色。
    2.根节点必须是黑色。
    3.红色的节点,它的叶节点只能是黑色。
    4.每条路径都有相同的黑色节点。
    5.叶节点统一是nul节点

红黑树变色逻辑:
    1.新插入节点一定是红色
    2.当插入节点的父节点是红色,且它的叔叔节点是红色时,把父节点和叔叔节点改为黑色,爷爷节点改为红色

红黑树左旋逻辑:
    父节点为红色,且叔叔节点为黑色,且当前节点在右子树上,则向左旋转

红黑树右旋逻辑:
    父节点为红色,且叔叔节点为黑色,且当前节点在左子树上,则向右旋转

mysql索引不使用hash结构的原因:不支持顺序和范围查找
    
mysql:
    聚簇索引(innodb主键索引):索引文件和数据文件是一起的,非叶子节点存放索引,叶子节点存放数据,使用辅助索引的时候先找到主键的key,通过主键的key再走一遍主索引
        查询速度快
        更新代价大
        依赖于有序数据
    非聚簇索引(mylsam,innodb的二级索引【普通索引、唯一索引、前缀索引、全文索引(innodb在mysql5.6才开始支持全文索引)】):索引文件和数据文件分开,叶子节点可能存放指针,也可能存放主键,所以缺点就是需要二次回表或者通过指针二次查询
        更新代价比聚集索引要小
        依赖于有序的数据
        可能会二次查询(回表)
索引类别:
    唯一索引、普通索引、前缀索引、全文索引
非聚簇索引一定回表查询吗,不一定,比如
    SELECT id FROM table WHERE id=1;
    SELECT name FROM table WHERE name='guang19';
    
数据库完整性:
    实体完整性:主键不能取空值
    参照完整性:外键
    域完整性/用户定义的完整性:字段属性的约束,包括字段的值域、字段的类型和字段的有效规则(如小数位数)等约束

b树和b+树的区别:
    b树(也叫b-树)所有节点都存放key和数据,b+树除了叶子节点其他节点都只存放key值,不存放数据,叶子节点存放所有的数据,b+树的叶子节点有一条链表
    
mysql日志:错误日志、查询日志、慢查询日志、事务日志、二进制日志
    二进制日志:binlog(归档,server层)、redolog(重做,用于服务器崩溃恢复,存储引擎层)、undolog(事务日志,用于数据回滚)

redolog刷盘机制(调用fsync对redolog进行日志保存):0提交到redo log buffer,不刷盘 1提交立即刷盘 2.提交到redo log buffer内容并写入 page cache
redolog刷盘条件:
    1.1秒轮询到达时刷盘
    2.redo log buffer到达一半时进行刷盘
redolog为什么需要先存入page cache    
    因为数据页大小是16KB,刷盘比较耗时,这时候如果打包刷盘肯定是更好的
binlog格式:row、mixed、statement,使用canal时候需设置为row,因为row的sql语句的时间是原始的时间,statement时间为now()

mysql保证数据已执行的2阶段提交流程:先记录redolog为预提交,然后提交是先提交binlog日志,再提交redolog日志,redolog日志提交先判断是否为commit阶段,如果不是在判断是否有binlog日志,如果有也进行commit

mvcc:俗称多版本并发控制,为了解决数据库事务并发访问出现的读写冲突问题,做到了不加锁并发读,实现原理为使用【隐藏字段】读取上一个【Read View】判断数据可见性,找到对应的【undo log】知道找到可见的记录

Read View作用(针对当前读,维护的是一个活跃列表):
    read view 主要是用来做可见性判断的,事务进行快照读操作的时候生成的读视图,它会判断每条记录的的数据,这条数据可能是真实的数据,也可能是undolog中的数据
    (主要是将要被修改的数据的最新记录中的 DB_TRX_ID(即当前事务 ID )取出来,与系统当前其他活跃事务的 ID 去对比(由 Read View 维护),如果 DB_TRX_ID 跟 Read View
    的属性做了某些比较,不符合可见性,那就通过 DB_ROLL_PTR 回滚指针去取出 Undo Log 中的 DB_TRX_ID 再比较,即遍历链表的 DB_TRX_ID(从链首到链尾,即从最近的一次修改查起),
    直到找到满足特定条件的 DB_TRX_ID , 那么这个 DB_TRX_ID 所在的旧记录就是当前事务能看见的最新老版本)

结构如下class ReadView {
        private:
            trx_id_t m_low_limit_id;      /* 大于等于这个 ID 的事务均不可见 */ maxid
            trx_id_t m_up_limit_id;       /* 小于这个 ID 的事务均可见 */ minid
            trx_id_t m_creator_trx_id;    /* 创建该 Read View 的事务ID */
            trx_id_t m_low_limit_no;      /* 事务 Number, 小于该 Number 的 Undo Logs 均可以被 Purge */
            ids_t m_ids;                  /* 创建 Read View 时的活跃事务列表 */ 在m_low_limit_id和m_up_limit_id的非活跃线程均可以被 Purge线程删除
            m_closed;                     /* 标记 Read View 是否 close */
        }
    
什么时候产生事务id什么时候产生ReadView:
    CUD操作才产生事务Id,S查询操作前才产生ReadView(RR级别在事务中只产生一次,RC每次select都会产生)
    
简述ReadView源码及判断可见性流程
    判断id小于m_up_limit_id(minid),如为true
    否则判断id大于等于m_low_limit_id(maxid),如为false
    否则判断id是否在活跃列表m_ids,如为,则false,否则true
    
MVCC和undolog及ReadView之间的关系梳理
    MVCC主要为解决读写冲突,依赖于undolog和ReadView,undolog主要用于事务回滚及当前读的情况下获取最新数据,readView视图主要作用维护了一个活跃事务列表,为在事务里在进行
    select读取操作时产生一个判断事务可见性的功能从而通过事务Id读取到该读的数据
    
undolog
    作用:提供事务回滚、提供mvcc找到并读取上一版本
    分为:insert undolog和update undolog,在修改和删除时记录提交不可以直接删除undolog,因为还需要提供mvcc支持,需等待purge线程调用删除,
    insert操作的记录只对自己事务可见,其它事务是不可见的,所以insert undo log可以在事务提交后直接删除而不需要进行purge操作

RC 和 RR 隔离级别下 MVCC 的差异
    在 RC 隔离级别下的 每次select 查询前都生成一个Read View (m_ids 列表) 
    在 RR 隔离级别下只在事务开始后 第一次select 数据前生成一个Read View(m_ids 列表)
    
联动云采用RC级别的考量:
    1.RR隔离级别下,存在间锁锁,导致出现死锁的几率比RC大的多
        例子:select * from test where id <3 for update;在RR隔离级别下,存在间隙锁,可以锁住(2,5)这个间隙,防止其他事务插入数据!而在RC隔离级别下,不存在间隙锁,其他事务是可以插入数据
    2.在RR隔离级别下,条件列未命中索引或条件列非索引列会锁表!而在RC隔离级别下,只锁行
        例子:number列为非索引列
            事务1:UPDATE test.test123 SET rule_no_version_old='1' WHERE number>8;
            事务2:UPDATE test.test123 SET rule_no_version_old='2' WHERE number=3;
            在RR隔离级别下,走非聚簇索引,进行全部扫描,事务2会被锁住,即将整个表锁上,RC隔离级别下,其先走聚簇索引,进行全部扫描,对对应的行加行锁
    3.在RC隔离级别下,半一致性读(semi-consistent)特性增加了update操作的并发性
        半一致性读:一个update语句,如果读到一行已经加锁的记录,此时InnoDB返回记录最近提交的版本,由MySQL上层判断此版本是否满足update的where条件。若满足(需要更新),
        则MySQL会重新发起一次读操作,此时会读取行的最新版本(并加锁),说白点:就是在update匹配到的那一行数据已经上锁了,此时需要读取当前数据
    4.RC隔离级别不支持 statement 格式的bin log,因为该格式的复制,会导致主从数据的不一致
    
MVCC在RR隔离级别下不能解决幻读的场景例子:
    begin;
    #假设users表为空,下面查出来的数据为空
    select * from users; #没有加锁
    注:#此时另一个事务提交了,且插入了一条id=1的数据
    select * from users; #读快照,查出来的数据为空
    update users set name='mysql' where id=1;#update是当前读,所以更新成功,并生成一个更新的快照
    select * from users; #读快照,查出来id为1的一条记录,因为MVCC可以查到当前事务生成的快照
    commit;
    结论:所以单纯靠MVCC在RR隔离级别下是不能解决幻读问题的,只能配合next-key间隙锁显示进行加锁控制才能解决幻读问题

mysql Server层数据结构:
    连接器、查询缓存、分析器、优化器、执行器

sql语句的执行过程
    查询语句的执行流程如下:权限校验(如果命中缓存)--->查询缓存--->分析器--->优化器--->权限校验--->执行器--->引擎 
    更新语句执行流程如下:分析器---->权限校验---->执行器--->引擎---redo log(prepare 状态)--->binlog--->redo log(commit状态)
    
B-树特点:
    1.所有键值分布在整颗树中(索引值和具体data都在每个节点里);
    2.任何一个关键字出现且只出现在一个结点中;
    3.每个节点可以存放多个key,节点中既存放key,也存放值
    4.搜索有可能在非叶子结点结束(最好情况O(1)就能找到数据);
    5.在关键字全集内做一次查找,性能逼近二分查找;
B+树与B-树的区别:
    1.B+树内节点不存储数据,所有 data 存储在叶节点导致查询时间复杂度固定为 log(n)。而B-树查询时间复杂度不固定,与 key 在树中的位置有关,最好为O(1)。
    2.B+树叶节点两两相连可大大增加区间访问性,可使用在范围查询等,而B-树每个节点 key 和 data 在一起,则无法区间查找。
为什么使用b+树而不是红黑二叉树:
    红黑二叉树高度高,进行的io次数就会更多,而b+树因为每个节点可以存储多个key,且每个节点都有多个字节点,树的高度不高,io次数都为logn,b+树可以用于做范围查找
hashMap为什么使用红黑二叉树不使用b+树
    b+树一般用于数据存在磁盘,红黑树一般用于数据存在内存,且b+树插入要维护链表、排序、索引,b+树插入速度更慢一些,但是查找相应比红黑树要方便

mysql索引一个节点可以存多少key:mysql一个B+树的节点是一页,一页16k(可以设置),对于非聚簇索引来说叶节点可以存放指针,对于聚簇索引叶子节点存放的是数据,如果聚簇索引key为一个bigint为8字节,指针在InnoDB中为6字节,一共14字节,那么第二层中一页可
存储16*1024/14=1170,一个节点1170,假设一条记录为1k,那么第三层的一个叶子节点可以存放16条记录,那么第三层就有1170*1170*16=21902400,所以总得来说是根据记录大小和存储器存储能力决定

mysql排序:
    当对sql进行order by排序的时候,需要尽可能的使用索引排序,如果无法使用索引排序的话,mysql就会使用文件排序
文件排序出现的几种情况:
    order by 字段不是索引字段
    order by 字段是索引字段,但是 select 中没有使用覆盖索引,如:select * from staffs order by age asc;
    order by 中同时存在 ASC 升序排序和 DESC 降序排序,如:select a, b from staffs order by a desc, b asc;
    order by 多个字段排序时,不是按照索引顺序进行 order by,即不是按照最左前缀法则,如:select a, b from staffs order by b asc, a asc;
文件排序分为:
    双路排序(又叫回表排序模式):先根据相应的条件取出相应的排序字段和可以直接定位行 数据的行 ID,然后在 sort buffer 中进行排序,排序完后需要再次取回其它需要的字段
    单路排序:是一次性取出满足条件行的所有字段,然后在sort buffer中进行排序
    双路排序和单路排序的区别:1.放入到排序缓存(sort_buff)中一个是放入全部数据,一个是放入排序字段数据进行排序 2.双路排序会根据id回表查询数据 3.单路排序使用更多的空间
mysql如何判断使用双路排序还是单路排序:
    查询字段是否大于max_length_for_sort_data,大于走双路,小于走单路
    
mysql字符集
    ASCII     
Mysql主从同步的实现原理:
    主节点:
        1.master先保存binlog
        2.当slave连接到master的时候,master机器会为slave开启binlog dump线程。当master 的 binlog发生变化的时候,binlog dump线程会通知slave,并将相应的binlog内容发送给slave
    从节点:
        当主从同步开启的时候,slave上会创建2个线程。
        I/O线程。该线程连接到master机器,master机器上的binlog dump线程会将binlog的内容发送给该I/O线程。该I/O线程接收到binlog内容后,再将内容写入到本地的relay log。
        SQL线程。该线程读取I/O线程写入的relay log。并且根据relay log的内容对slave数据库做相应的操作。
        
InnoDB 存储引擎的文件格式是.ibd 文件,数据会按照表空间(tablespace)进行存储,分为共享表空间和独立表空间
    共享表空间:多个数据表共用一个表空间,同时表空间也会自动分成多个文件存放到磁盘上
    独立表空间:每张表都相互独立,不会影响到其他数据表
    
binlog没有记录如何做数据同步
    1.如果我们之前没有做过全量备份,也没有开启 Binlog,那么我们还可以通过.ibd 文件进行数据恢复,采用独立表空间的方式可以很方便地对数据库进行迁移和分析。如果我们误删除(DELETE)某个数据表或者某些数据行,
    也可以采用第三方工具 Percona Data Recovery Tool for InnoDB找回数据
    2.如果已经开启了 Binlog,就可以使用闪回工具,比如 mysqlbinlog 或者 binlog2sql

.ibd文件损坏了,数据如何找回
    设置innodb_force_recovery参数进行强制恢复,数据备份完了之后,关闭innodb_force_recovery,并重启数据库

如何强制恢复:
    innodb_force_recovery参数一共有 7 种状态,除了默认的 0 以外,还可以为 1-6 的取值,分别代表不同的强制恢复措施,一般设置成1,可以读取数据表,并且对当前损坏的数据表进行分析和备份

大表结构修改操作:(大表操作产生的mdl写锁失败如何解决)
    1.安装percona-toolkit插件。使用pt-online-schema-change命令。原理:建立一张新表,在新表中做表结构的修改,然后把旧表的数据copy到新表
    2.在晚上低峰时进行操作
    3.建立分表,或者做冷热处理,使单表数据量减少
    
聚集索引的缺点:
    依赖于有序的数据 :因为 B+树是多路平衡树,如果索引的数据不是有序的,那么就需要在插入时排序,如果数据是整型还好,否则类似于字符串或 UUID 这种又长又难比较的数据,插入或查找的速度肯定比较慢。 
    更新代价大 : 如果对索引列的数据被修改时,那么对应的索引也将会被修改,而且况聚集索引的叶子节点还存放着数据,修改代价肯定是较大的,所以对于主键索引来说,主键一般都是不可被修改的。
    
redis缓存策略
    1.旁路缓存模式(订单主信息缓存都是使用该模式)
        写 :
            先更新DB
            然后直接删除cache。
        读 :
            从 cache 中读取数据,读取到就直接返回
            cache中读取不到的话,就从 DB 中读取数据返回
            再把数据放到 cache 中
    2.读写穿透(配置信息都是使用该类缓存模式)
        写(Write Through): 
            先查 cache,cache 中不存在,直接更新 DB。 
            cache 中存在,则先更新 cache,然后 cache 服务自己更新 DB(同步更新 cache 和 DB)
        读(Read Through):
            从 cache 中读取数据,读取到就直接返回 。
            读取不到的话,先从 DB 加载,写入到 cache 后返回响应。
    3.异步缓存写入
        写: 则是只更新缓存,不直接更新 DB,而是改为异步批量的方式来更新 DB
        读: 同步更新 cache 和 DB
redis:redis在io模型中属于多路复用模型
redis不使用多线程的原因
    1.单线程编程容易并且更容易维护;
    2.Redis 的性能瓶颈不在CPU,主要在内存和网络;
    3.多线程就会存在死锁、线程上下文切换等问题,甚至会影响性能。
redis支持多线程吗:redis6.0支持多线程,提高网络IO读写性能
过期的数据的删除策略:惰性删除 、定期删除
redis淘汰策略保持热点数据:lru(删除最近最少使用)、ttl(删除即将到期的数据)、random(随机删除)、lfu(以次数作为计量,删除最少被使用的)
    no-eviction
    当内存不足以容纳新写入数据时,新写入操作会报错,无法写入新数据,一般不采用。
    allkeys-lru
    当内存不足以容纳新写入数据时,移除最近最少使用的key,这个是最常用的
    allkeys-random
    当内存不足以容纳新写入的数据时,随机移除key
    allkeys-lfu
    当内存不足以容纳新写入数据时,移除最不经常(最少)使用的key
    volatile-lru
    当内存不足以容纳新写入数据时,在设置了过期时间的key中,移除最近最少使用的key。
    volatile-random
    内存不足以容纳新写入数据时,在设置了过期时间的key中,随机移除某个key 。
    volatile-lfu
    当内存不足以容纳新写入数据时,在设置了过期时间的key中,移除最不经常(最少)使用的key
    volatile-ttl
    当内存不足以容纳新写入数据时,在设置了过期时间的key中,优先移除过期时间最早(剩余存活时间最短)的key。

LRU实现原理:
    1. 新数据插入到链表头部;
    2. 每当缓存命中(即缓存数据被访问),则将数据移到链表头部;
    3. 当链表满的时候,将链表尾部的数据丢弃。
redis如何持久化:RDB快照副本二进制文件、AOF文件追加写操作(前者将当前数据保存到硬盘,后者则是将每次执行的写命令保存到硬盘)
redis的RDB、AOF共存下恢复流程:先判断AOF是否存在,存在直接加载AOF,不存在判断是否存在RDB,存在加载RDB
RDB、AOF优缺点
    RDB:全量数据快照,文件小,恢复快,但无法保存最近一次快照之后的数据
    AOF:可读性高,适合报错增量数据,数据不易丢失,但文件体积大,恢复时间长
RDB-AOF混合持久化方式;(RDB做镜像持久化,AOF增量持久化)
    RDB中使用bgsave保存全量的数据,在保存的过程中可能会有增量的数据进来,这个时候AOF就进行记录保存,然后全量+增量 这样就快速完美的保存了所有的数据。
redis什么情况下会进行全量同步
    1.第一次设置slave服务器并建立同步连接时,会发送sync命令,master启动一个后台进程,将数据快照保存到文件并同步给slave
    2.slave断开连接重连的时候
    3.在设置的周期同步时调用sync命令给master,进行全量同步
redis什么情况下进行增量同步
    1.在进行完全量同步时,后续的增量数据进行增量同步即可
总结:主从刚刚连接的时候,进行全量同步;全同步结束后,进行增量同步

redis数据类型及原理:
    String:SDS动态字符串,属于二进制安全字符串,意思是redis可以包含任何数据,不会特殊处理任意一个字符,包括特殊字符(C语言中字符串是以特殊字符“\0”来作为字符串的结束标识)
        int:使用整形值实现的字符串对象         存储数值类型
        raw动态字符串实现的字符串对象        大于44个字节
        embstr使用embstr编码的动态字符串实现(SDS数据结构)    小于44个字节
    List:按元素插入顺序排列,quicklist快速链表(双向链表,由链表和ziplist结合,即按链表的节点分,每个节点使用压缩链表存储)
        quicklist:ziplist+双向链表的结合,双向链表内存不连续,容易产生碎片,内存占用大,需要存储前驱和后继节点
        ziplist:一整块连续的内存,存储效率高,不适用于大数据量的存储
    Hash:ziplist(存储比较少的字符和数据,节点紧凑存储,内存空间分配是连续的)和hashtable
        zipList:条件(1;哈希对象的键和值的字符串长度都小于64字节,2.保存的数量小于512)
        hashtable:dict的数据结构
            为什么使用hashtable【数组】而不用map【红黑树】:hashtable存储和搜索速度比map快
            Hashtable的扩容:通过rehash,当数量大于length*0.75时进行扩容,扩容为length*2+1    
    Set:无序的不允许重复的数据结构
        inset:可以理解为整数数组,只是这个数据结构是独立的struct
            1.数据元素都是整形
            2.元素个数小于set-max-inset-entries,默认512
        hashtable:dict数据结构
    ZSet:ziplist、(skiplist+dict),有序、不允许重复,ziplist转换成skiplist之后不可逆,
        ziplist:
            有序集合保存的元素数量小于默认值128个
            有序集合保存的所有元素的长度小于默认值64字节
        skiplist+dict:
            为什么用跳表不用红黑树:跳表范围查找更快、跳表容易实现,红黑树需要通过旋转保持平衡
            dict数据结构:其实就是有索引信息的hashtable
            skipList:跳表,通过不同层级的进行类二分查找
            dict和skipList的协同工作:两种数据结构通过指针来共享元素,所以不存在内存浪费,但是两个又都是独立的数据结构,只是共享了数据
            为什么用了skipList还用dict:根据redis的需求而定,skiplist的优势在于范围查找,无需排序;dict的优势在于查找成员的分值时,时间复杂度为O(1),但是字典结构是hashtable,是无序的,如果类似范围查找则需要进行排序
            什么场景下使用dict,什么场景下使用skipList:

redis的底层数据结构:
    SDS动态字符串
    链表
    字典
    跳跃表
    整数集合
    压缩列表
    
redis缓存雪崩、缓存穿透、缓存击穿
    缓存击穿(单个key失效):是针对缓存中没有但数据库有的数据,当前key是一个热点key(例如一个秒杀活动),并发量非常大,在缓存失效的瞬间,有大量线程来重建缓存,造成后端负载加大,甚至可能会让应用崩溃。
        解决方案:分布式互斥锁(在查到缓存没有和即将查询db中间加上redis锁)、永不过期(需要配合淘汰策略)
    缓存雪崩(多个key失效):是指大量Key同时失效,对这些Key的请求又会打到DB上,同样会导致数据库压力过大甚至挂掉。
        解决方案:1.缓存高可用、2.采用多级缓存,本地进程作为一级缓存,redis作为二级缓存、3.尽量让不同的key的过期时间不同
    缓存穿透(缓存和数据库都没有数据):是针对数据库和缓存中都没有的数据。
        解决方案:缓存空对象、布隆过滤器
        
redis缓存一致性解决方案:(缓存的数据值≠数据库中的值;缓存或者数据库中存在旧值,导致其他线程读到旧数据)
    读写请求串行化(数据强一致)
        写请求和读请求前都需要获取到分布式锁
    延时双删策略(最终一致性)
        写请求在更新数据库的前后均删除一次缓存,第二次删除需要延迟一段时间
        步骤1:删除缓存
        步骤2:更新数据库
        步骤3:延迟一定的时间
        步骤4:再次删除缓存
        步骤4再次删除的原因:可以删除可能在步骤1到2之间被读请求存入的脏数据。
        步骤3延迟一定时间的原因:
            (1)保证步骤1和2之间的读请求正常执行,让步骤4可以有效删除脏数据。
            (2)如果数据库是主从模式,从库数据更新需要一定时间,防止读请求在没有缓存的情况下,读出从库中的旧数据放在缓存中
    基于订阅binlog的同步机制(最终一致性)
        MySQL产生新的更新操作(添加,删除等等),就可以把binlog相关消息推送(canal,kafka,rabbitMQ)至缓存服务(Redis),Redis根据binlog中的记录更新自身数据
canal同步es数据一致性保证
    客户端ACK机制,当出现异常时,将异常捕获,记录下那次批处理失败,记录批处理失败的Id,当达到失败的阈值的时候进行失败回滚并发送告警邮件
        
redis持久化方式:
    save。同步操作,会阻塞Redis。
        描述 aof 处理增量数据
        缺点
            致命的问题,持久化的时候redis服务阻塞(准确的说会阻塞当前执行save命令的线程,但是redis是单线程的,所以整个服务会阻塞),不能继对外提供请求,
    bgsave。调用linux的fork(),然后使用新的线程执行复制。但是fork期间也会阻塞Redis,但是阻塞时间通常很短。
        原理:fork() + copyonwrite
        描述  rdb 处理全量数据
        优点
            他可以一边进行持久化,一边对外提供读写服务
    自动保存。Redis配置文件中设置了自动保存的触发机制,可以自定义修改,运行原理同bgsave
    
redis集群:
    主从
        高可用:可以实现读写分离,数据备份。但是并不是「高可用」的
    哨兵:高可用
        集群结构:n个(master+slave+sentinel)
        哨兵模式引入了一个Sentinel系统去监视主服务器及其所属的所有从服务器。一旦发现有主服务器宕机后,会自动选举其中的一个从服务器升级为新主服务器以达到故障转移的目的
        高可用:主从模式的「高可用」版本,其引入了Sentinel对整个Redis服务集群进行监控。但是由于只有一个主节点,因此仍然有写入瓶颈
        哨兵功能的总结:
            每个 Sentinel 可监视任意多个服务器,每个服务器也可被任意多个 Sentinel 监视;
            多个监视同一主服务器的 Sentinel 视为一个集群,在被监视主服务器下线后,该集群将选举出一个 Sentinel Leader,由该 leader 对其进行故障转移
        当有master挂壁了(
            master是否挂了:先通过一个sentinel判断一个master是否在一个时间内响应主观下线,询问其他哨兵节点是否挂了,足够多的哨兵说挂了就进行客观下线,客观下线之后进行故障转移
            故障转移:
                选举 Sentinel leader,通过raft半数机制选举,用于执行故障转移,
                由 Sentinel leader 从故障主服务器的所有从服务器中选一个做新的主服务器
                向选出的新master 发送 slaveof no one命令将其变成 master
                向其它slave服务器发送slaveof命令,让它们复制新的服务器
                若下线的主服务器上线,则发送slaveof命令,让其降级为从服务器,复制新的主服务器
        哨兵模式正常的读命令和写命令流程:
            master服务可读写,slave服务为只读,当master服务接受到数据修改或写入的命令时,会异步将命令发送到slave上,以此保持master与slave上数据的一致性
            具体流程:
                redis的复制功能的实现,包括 同步(SYNC/PSYNC) 和 命令传播(command propagate) 两个操作:
                    在刚开启主从同步功能或者断线重连时,使用同步命令让slave的数据状态跟master保持一致
                        SYNC命令:redis2.8之前使用,slave向master发送sync命令,master会执行bgsave命令生成rdb文件,然后把rdb文件发送给slave,slave通过rdb恢复数据,达到与master一致的状态
                        PSYNC命令:因为SYNC命令只能执行全量同步,对于slave断线重连后只需要执行部分重同步就可以达到一致的情况,仍使用SYNC命令有很大的浪费,因为在【redis2.8】中,提供了PSYNC命令来取代SYNC命令
                    同步后,若master执行写命令,状态又将不一致,通过命令传播让slave执行同样的命令,使状态保持一致
            
    cluster分片:目的内存的高拓展
        集群结构:(1个master+n个slave)+(1个master+n个slave)......
        高可用方面:提供了主从高可用,同时数据是分片保存在各个节点中的,可以支持高并发的写入与读取。当然实现也是其中最复杂的
        去无心节点方式实现,集群将会通过分片方式保存数据库中的键值对
        分片与槽位:
            Redis Cluster的整个数据库将会被分为16384个哈希槽
        Redis Cluster的高可用
            Redis的每个节点都可以分为主节点与对应从节点。主节点负责处理槽,从节点负责复制某个主节点,并在主节点下线时,代替下线的主节点
        cluster读写流程:
            该槽归自己处理,进行处理;
            该槽不归自己处理,返回一个MOVED错误,并把负责处理该槽的节点信息返回,引导客户端向正确的节点请求服务;
            该槽数据目前正在迁移:
            首先在自己的库中查找该键,若存在,处理;
            若不存在,返回ASK错误,并给出新节点信息,引导客户端向新节点请求服务;客户端需要先向新节点发送asking命令,再发送正式的命令,否则将会收到一个moved错误
        如何计算嘈的位置:
            Redis 客户端通过 CRC16(key)%16383 计算出 Slot 的值
        Redis集群中节点的通信机制:
            goosip协议,节点之间通讯的目的是为了维护节点之间的元数据信息。这些元数据就是每个节点包含哪些数据,是否出现故障,通过gossip协议,达到最终数据的一致性
        gossip协议常见的消息类型包含: 
            ping、pong、meet、fail等等
        集群的故障检测
            主观下线(pfail):当节点A检测到与节点B的通讯时间超过了cluster-node-timeout 的时候,就会更新本地节点状态,把节点B更新为主观下线。主观下线并不一定真正下线,需要判断客观下线
            客观下线:当半数以上的主节点标记了节点B是主观下线时,便会触发客观下线的流程(该流程只针对主节点,如果是从节点就会忽略)
        故障转移:    
            选出master:
                故障节点下线后,如果是持有槽的主节点则需要在其从节点中找出一个替换它,从而保证高可用。此时下线主节点的所有从节点都担负着恢复义务,这些从节点会定时监测主节点是否进入客观下线状态,
                如果是,则触发故障恢复流程。故障恢复也就是选举一个节点充当新的master,选举的过程是基于Raft协议选举方式来实现的
            数据转移:
                (1)被选中的从节点执行SLAVEOF NO ONE命令,使其成为新的主节点
                (2)新的主节点会撤销所有对已下线主节点的槽指派,并将这些槽全部指派给自己
                (3)新的主节点对集群进行广播PONG消息,告知其他节点已经成为新的主节点
                (4)新的主节点开始接收和处理槽相关的请求
                备注:如果集群中某个节点的master和slave节点都宕机了,那么集群就会进入fail状态,因为集群的slot映射不完整。如果集群超过半数以上的master挂掉,无论是否有slave,集群都会进入fail状态。
    
面试题:    
Jedis与Redisson对比有什么优缺点?
    Jedis是Redis的Java实现的客户端,其API提供了比较全面的Redis命令的支持;Redisson实现了分布式和可扩展的Java数据结构,和Jedis相比,功能较为简单,不支持字符串操作,不支持排序、事务、管道、
    分区等Redis特性。Redisson的宗旨是促进使用者对Redis的关注分离,从而让使用者能够将精力更集中地放在处理业务逻辑上。Redisson支持一些java对象操作,如Bloom filter, BitSet, Set, SetMultimap等
    
Redis事务相关的命令有哪几个
    MULTI、EXEC、DISCARD、WATCH
    
redis分区方案:
    查询路由:客户端随机地请求任意一个redis实例,然后由Redis将请求转发给正确的Redis节点
    代理分区:代理分区意味着客户端将请求发送给代理,然后代理决定去哪个节点写数据或者读数据。
    
修改配置不重启Redis会实时生效吗?
    针对运行实例,有许多配置选项可以通过 CONFIG SET 命令进行修改,而无需执行任何形式的重启。 从 Redis 2.2 开始,可以从 AOF 切换到 RDB 的快照持久性或其他方式而不需要重启 Redis。
            
redis应用场景:
    做订单缓存:使用hash存储对象,使用String存储字符串
    做分布式锁:setnx+expire+uuid解决服务器crash问题
    做队列:消息重试机制,rocketmq消息发送失败放到redis队列中重复消费,rPush放入队列,lPop取出数据,如果失败继续push,总共重试次数为15次,一次取1000条,定时线程5分钟执行一次
        一般来说lpop没有消息的时候需要sleep一下,订单这边并没有sleep,而是采用定时线程,如果不用定时线程,也可以用blpop,没有数据的时候线程阻塞知道数据到来,而如果需要生产一次消费多次,可以采用发布订阅模式,如果有数据丢失问题比如使用rocketmq存储
    做自定义消息定时任务发送到Rocketmq(18个延时级别):使用zset数据类型,判断当前消息延迟级别是否落在rocketmq的延迟级别之上,没有调用addZset放到redis中,其中分数为通知时间与当天0点的毫秒差值,
        采用定时任务rangeByScore获取获取消息,判断是否满足rocketmq的延迟级别,如果满足,直接放到rocketmq消息队列中并调用zrem删除元素
        
redis分布式锁出现的问题及解决方案:
    非原子操作
        场景:setNx和expire操作非原子性,如果setnx成功,expire设置失败,则key都是永久不失效,会导致内存不足
        解决方案:set命令解决,set命令可以进行原子操作, jedis.set(lockKey, requestId, "NX", "PX", expireTime)
    忘了释放锁
        场景:未手动释放锁
        解决方案:业务完成手动释放锁
    释放了别人的锁
        场景:不用服务之间或者多线程场景,线程A和线程B,A加锁成功并设置了超时时间,B等待A业务完成,此时A业务功能耗时很长,A的锁已到超时时间并自动释放了,此时B进来,B获取到了锁,此时A业务已经完成了进行释放锁,此时A释放的锁有可能就是B的锁
        解决方案:
            1.增加一个requestId,比如uuid,在加锁的时候放入到ThreadLocal中,在解锁的时候判断当前锁的uuid是不是自己的key,是则释放  
            2.使用redission看门狗对锁续期  
            3.lua脚本,查询锁是否存在和删除锁做原子操作
    大量的失败请求
        场景:秒杀场景、多线程文件目录创建
        解决方案:自旋锁,为规定的时间内进行自旋操作,自旋到一定次数还未获取到锁则直接返回失败
    锁重入问题
        场景:递归过程中第一层获取到了锁,在第二层中获取锁失败异常
        解决方案:使用可重入锁,比如使用redission的可重入锁
            原理:锁没重入一次计数加1,释放锁的时候计数减1
    锁竞争问题
        场景:在读的场景比较多的情况下,锁的粒度大,导致的性能低下
        解决方案:
            1.采用读写锁,保证读写、写写并发操作上锁是互斥的,比如采用redssion锁,内部已经实现了读写锁的功能
            2.锁分段,将一个大锁拆分成多个子锁(注:锁分段会造成业务系统复杂度增加,需要维护多个锁)
    锁超时问题
        场景:线程A业务功能在进行中,锁超时了
        解决方案:
            1.redission看门狗模型,使用守护线程定时任务TimerTask自动续期,设置一个总的超时时间,如果在总的超时时间还没有执行完就不再续期了
    主从复制问题
        场景:主节点负责写,从节点负责读,此时线程A在原来的主节点上进行加锁,加锁完成之后主节点挂了,没有把锁信息复制到对应的从节点,然后从节点升级为了主节点,那么从节点中没有锁的信息,线程B进来在新竞的主节点上加锁成功
        解决方案:
            1.使用redssion的RedissonRedLock锁,存储多个redis节点的信息,并对redis中所有节点(至少一半节点)进行加锁,如果所有节点(至少一半节点)加锁成功则成功,否则失败(这种情况会出现数据不一致的情况,如至少一半成功,
              有可能其他节点会失败,对这么多节点加锁也有性能损耗),此方案只保证了AP,不保证CP
            2.使用zookeeper的分布式锁,但会造成服务不可用,即在CAP定理中,zk只保证CP,不保证AP
            总结:在大部分分布式锁的使用情况看,只使用一般的redis分布式锁就可以了,数据不一致可以采用最终一致性来解决,但是服务不可用对用户来说是最不好的体验
            
redis与pipeline(pipeline在命令行中没有,但是是支持pipeline的)
    RTT概念:redis客户端执行一条命令分4个过程,发送命令-〉命令排队-〉命令执行-〉返回结果,这个过程称为Round trip time(简称RTT, 往返时间)
    pipeline引入背景:mget mset有效节约了RTT,但大部分命令(如hgetall,并没有mhgetall)不支持批量操作,需要消耗N次RTT ,这个时候需要pipeline来解决这个问题
    原理:队列,将一批命令打包到一个内部维护的queue里,然后建立socket与server交互,这时候是只会发送一次命令,也就是只会交互一次,然后queue内的命令都执行完后会一起返回结果,这样大大减少了通信的次数,自然降低了通信所耗费的时间
        比如原来:n条命令(n次rtt)=n次网络时间+n次命令计算时间,现在一次pipeline(n条命令)=一次网络传输+n次命令计算时间

MongoDb组件:
    Collections(相当于table)、Document(相当于record记录)、database
支持数据类型:可以为:null、boolean、String、Object、Array等
1.什么是MongoDB
    MongoDB是一个文档数据库,提供好的性能,领先的非关系型数据库。采用BSON存储文档数据(BSON()是一种类json的一种二进制形式的存储格式,简称Binary JSON)
命名空间:
    mongodb存储bson对象在丛集(collection)中.数据库名字和丛集名字以句点连结起来叫做命名空间(namespace)
索引:
    索引的存储数据结构是B树,索引命名空间存储着对B树的根节点的指针。
为什么要在MongoDB中用"Code"数据类型
    "Code"类型用于在文档中存储 JavaScript 代码。
MongoDB中使用"Object ID"数据类型    
    "ObjectID"数据类型用于存储文档id,有四部分组成:时间戳、客户端ID、客户进程ID、三个字节的增量计数

    
Srping
bean的作用域:singleton、prototype、request、session、global-session(其中,request、session和global-session这三个作用域只有在web开发中才会使用到。)

什么情况下使用prototype: 
        有实例变量的类
bean 的生命周期:实例化bean--填充属性--初始化bean
        实例化一个bean-->设置对象属性(pupulateBean)-->检查aware相关接口-->调用前置beanPostProcessor处理-->检查是否实现初始化接口-->检查配置是否有init-method-->调用后置beanPostProcessor处理-->使用中-->检查是否实现DisposableBean接口-->调用destory方法
Spring事务:
        分为编程式事务和声明式事务
        事务属性分为
        事务传播方式
            TransactionDefinition.PROPAGATION_REQUIRED如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务
            TransactionDefinition.PROPAGATION_REQUIRES_NEW创建一个新的事务,如果当前存在事务,则把当前事务挂起。也就是说不管外部方法是否开启事务,Propagation.REQUIRES_NEW修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不干扰
            TransactionDefinition.PROPAGATION_NESTED:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行,即大的包含小的,小的回滚不包含大的;如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED
            TransactionDefinition.PROPAGATION_MANDATORY如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常
        回滚规则:默认情况下,事务只有遇到运行期异常(RuntimeException 的子类)时才会回滚,Error 也会导致事务回滚,但是,在遇到检查型(Checked)异常时不会回滚,此时需要try..catch手动抛出异常,
            Spring的中对SqlException的处理是先捕获并转换成TransientDataAccessException异常,如果TransientDataAccessException转换失败,还有兜底异常RecoverableDataAccessException,其中这两个异常都是继承自DataAccessException
        隔离级别:与sql一样,ru、rc、rr、序列化(读取每行都进行加锁)
        事务超时时间
        是否只读:readonly属性一旦被设置后,数据库级别如果为提交读,那么同一个事务中,如果对两次结果集进行查询,中间间隔修改数据库,那么应该会是同一个结果集,相当于查询的时候采用的是重复读的隔离级别
    
错误的事务用法:
    TransactionDefinition.PROPAGATION_SUPPORTS: 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
    TransactionDefinition.PROPAGATION_NOT_SUPPORTED: 以非事务方式运行,如果当前存在事务,则把当前事务挂起。
    TransactionDefinition.PROPAGATION_NEVER: 以非事务方式运行,如果当前存在事务,则抛出异常。
    
什么情况下事务不会回滚:
    1.访问权限问题,非public
    2.方法用final修饰,spring事务基于AOP,方法被final修饰后不能被重写
    3.方法内部使用
    4.多线程调用,spring的事务是在一个连接中,而连接是存在ThreadLocal中,多线程的会就会出现问题
    5.数据库本身就不支持事务,如Myasim
    6.错误的传播特性
    7.错误且自定义回滚规则
    8.自己吞了异常或者手动抛了其他的错误回滚规则异常
    
事务注解原理:
    @Transactional 的工作机制是基于 AOP 实现的,AOP 又是使用动态代理实现的。如果目标对象实现了接口,默认情况下会采用 JDK 的动态代理,如果目标对象没有实现了接口,会使用 CGLIB 动态代理。
    
面试问题Synchronized和@Transactional同时使用,有什么问题?
    @Transactional的事物是Spring的AOP开启的。进入这个方法前,AOP会先开启事物,然后进入方法,此时会加锁,当方法结束后,锁释放,然后才会提交事物。如果在释放锁和提交事物之间有其他线程请求,然后该线程继续加锁,这种情况会导致程序不安全。
    所以应该在开启事物前加锁或者在事务中只锁定某一个需要锁定的代码块
    例子:    
        @Transactional(rollbackFor=Exception.class)
        public synchronized String demo(String req)throws Exception {
            System.out.println("demo.......");
        }
    通俗讲:流程本身是这样,开启事务--执行同步代码块--同步代码块执行完毕--提交事务,即在同步代码块完毕释放锁到提交事务这中间的过程会导致线程不安全
        
Spring Boot 自动装配原理:spi
Spring ioc流程
        通过XmlBeanFactroy进行XML配置的验证,获取Document,注册好beanDefinitions,对beanDefinitions标签进行解析得到AbstractBeanDefinion,这中间包括自定义标签的解析和默认标签的解析
    完成bean的解析后进行bean的加载,加载过程中会将beanDefinitions转换为rootBeanDefinition,调用getBean方法,如果从缓存中得到了bean直接返回,没有获取到会调用createBean方法创建,调用doCreateBean进行bean的实例化,调用createBeanInstance简单初始化,通过
    pupulateBean方法进行属性的填充,此时如果是创建单例bean的时候如果存在循环依赖,则需要解决循环依赖,得到的完整的bean将会被放入到一级缓存,并删除二三级缓存,bean的创建完成之后就是剩余的bean的生命周期的事了,initializeBean进行ioc的初始化
    
AbstractApplicationContext的refresh函数载入过程(在创建IoC容器前,如果已经有容器存在,则需要把已有的容器销毁和关闭,以保证在refresh之后使用的是新建立起来的IoC容器)
    1.调用prepareRefresh初始化,获取容器的当时时间,同时给容器设置同步标识 
    2.调用obtainFreshBeanFactory启动refreshBeanFactory,获取到beanDefinition(获取解析器及aop代理创建器等)
    3.调用prepareBeanFactory配置一些beanFactory特性,如类加载的特性等等
    4.调用postProcessBeanFactory指定一些BeanPost事件
    5.调用invokeBeanFactoryPostProcessors
    6.registerBeanPostProcessors监听后置处理
    7.调用initMessageSource初始化信息源
    8.调用initApplicationEventMulticaster初始化容器事件传播器
    9.调用子类的某些特殊Bean初始化方法onRefresh
    10.为事件传播器注册监听registerListeners
    11.调用finishBeanFactoryInitialization,初始化剩下的单例bean(此方法包含了ioc单例bean初始化、注解支持、BeanPostProcessor的执行、Aop的入口)
    12.调用finishRefresh,完成发布容器的生命周期
    13.发生异常后回调用destory方法和cancelRefresh,重置同步标志
    
Spring AOP实现方式
    通过AspectJ实现
        声明切面和切点,经过前置或者后置、环绕等方式进行通知
    通过Advisor增强,通过jdk或者cglib方式代理
        spring容器启动,每个bean的实例化之前都会先经过AbstractAutoProxyCreator类的postProcessAfterInitialization()这个方法
        getAdvicesAndAdvisorsForBean方法会提取当前bean的所有增强方法,然后获取到适合的当前bean 的增强方法,然后对增强方法进行排序,最后返回
        调用createProxy方法,创建代理。首先找到增强方法和增强处理器,先创建代理工厂proxyFactory,然后获取当前bean的增强器advisors,把获取到的增强处理器放到proxyFactory,最后创建jdk或者cglib的代理工厂
Spring aop在refresh容器入口流程:
        1.获取解析器
        AbstractApplicationContext#refresh-->obtainFreshBeanFactory-->refreshBeanFactory-->loadBeanDefinitions-->XmlBeanDefinitionReader#loadBeanDefinitions->doLoadBeanDefinitions-->registerBeanDefinitions-->doRegisterBeanDefinitions
        -->parseBeanDefinitions(实际解析beanDefinitions)-->parseCustomElement-->NamespaceHandler#resolve->AopNamespaceHandler#init->AopNamespaceHandler#parse(AopNamespaceHandler#init注册解析器)-->AspectJAutoProxyBeanDefinitionParser#parse(在解析配置文件时一旦遇到aspectj-autoproxy就会使用该解析器)
        2.获取代理创建器,(接着上面的方法)进行parse解析,自动注册代理创建器AnnotaAwareAspectJAutoProxyCreator
        AspectJAutoProxyBeanDefinitionParser#parse->AopNamespaceUtils#registerAspectJAnnotationAutoProxyCreatorIfNecessary->AopConfigUtils#registerAspectJAnnotationAutoProxyCreatorIfNecessary->registerAspectJAnnotationAutoProxyCreatorIfNecessary
        3.创建aop代理(AnnotaAwareAspectJAutoProxyCreator)(aop的实现是通过 postBeanProcess后置处理器,在初始化之后做代理操作的)
        refresh-->finishBeanFactoryInitialization->preInstantiateSingletons->getBean->doGetBean-->getSingleton->createBean->doCreateBean->initializeBean->AnnotaAwareAspectJAutoProxyCreator#postProcessAfterInitialization-->wrapIfNecessary
        -->createProxy(获取增强方法和处理器进行代理)
            3.1.获取增强处理器
            AbstractAutoProxyCreator#getAdvicesAndAdvisorsForBean-->findCandidateAdvisor
            3.2.创建代理createProxy
                createProxy->ProxyFactory.getProxy()->ProxyCreatorSupport.createAopProxy()->DefaultAopProxyFactory.createAopProxy()(proxy-target-class当为true为cglib,否则为jdk)
                
Spring Autowired源码:
    refresh--finishBeanFactoryInitialization-getBean--doCreateBean--populateBean--autowireByName/autowireByType---AutowiredAnnotationBeanPostProcessor#postProcessPropertyValues--findAutowiringMetadata--inject--
    applyPropertyValues--BeanDefinitionValueResolver#resolveValueIfNecessary--resolveReference--getBean
                
Spring使用多级缓存原因:
    1.管理不同状态下的bean
    2.循环依赖的问题: a依赖b,b依赖a
为什么使用三级缓存:
    解决代理对象(如aop)循环依赖的问题(因为Aop通过singleFactory.getObject()得到的对象如果是两次去取会得到不一样的对象,从而使早期bean与最终不一致问题)。
    
单单使用一级别缓存无法解决正常的循环依赖问题
使用二级缓存(singletonObjects、singletonFactories[原有的三级])无法解决AOP代理中出现的循环依赖问题:
    例: 1.a依赖b,b依赖a,c。c又依赖a。a,b,c均aop增强
    先加载a,实例化的bean放到singletonFactories,填充b属性,b进行实例化,b放到singletonFactories,填充a,从a中singleFactory.getObject()拿到实例化的a填充,填充c,c进行实例化,c放到singletonFactories,
    c进行a的填充,从a中singleFactory.getObject()拿到实例化的a填充【问题就出现在这里,这里拿到的a和b中填充的a不是一个对象,通过jdk/cglib代理会生成一个新的a代理对象】,接下来就是正常的操作
使用二级缓存:singletonObjects+earlySingletonObjects
    例: a依赖b,b依赖a,同时a,b都被aop增强。
    如果只使用二级缓存,且二级缓存缓存的是一个不完整的bean,这个时候a在设置属性的过程中去获取b(这个时候a还没有被aop的后置处理器增强【getEarlyBeanReference这个方法在设置属性之前,advice动态织入bean中】),创建b的过程中,b依赖a,b去缓存中拿a拿到的是没有经过代理的a。就有问题

spring的bean线程安全问题:
    如果bean有状态(有实例变量的类,可以保存数据),则线程不安全
    如果bean无状态,则线程安全
    固:对有状态的 bean 要使用 prototype,作用域对无状态的 bean 使用 singleton 作用域
spring重要接口:
    InitializingBean:refresh-->finishBeanFactoryInitialization-->initializeBean-->invokeInitMethods
    BeanPostProcesor:refresh-->finishBeanFactoryInitialization-->initializeBean-->applyBeanPostProcessorsBeforeInitialization/applyBeanPostProcessorsAfterInitialization
    DisposableBean:在使用完之后调用
    ApplicationContextAware:setApplicationContext() 在 Bean 初始化结束后被调用
        setApplicationContext() 在 Bean 初始化结束后被调用, 获取到spring容器的上下文,分两步:refresh-->prepareBeanFactory配置了ApplicationContextAwareProcessor处理器  refresh-->finishBeanFactoryInitialization-->initializeBean-->ApplicationContextAwareProcessor#applyBeanPostProcessorsBeforeInitialization#invokeAwareInterfaces 
    ApplicationListener:refresh-->finishRefresh
        分两步:finishRefresh中调用,初始化:refresh-->initApplicationEventMulticaster 注册阶段:refresh-->registerListeners-->multicastEvent-->doInvokeListener,其中multicastEvent使用同步或者异步方式调用对于listener的onApplicationEvent事件
    BeanNameAware:setBeanName(String name) 方法在 Bean 初始化后被调用,
        refresh-->finishBeanFactoryInitialization-->initializeBean-->invokeAwareMethods

SpringBoot main方法执行完毕为什么程序还在继续
    其使用内置的 tomcat容器运行,tomcat运行之后有一个 initialize 方法,里面有一个 while(stopAwait),一直在检查,因此并不会退出
    public void await() {
        // ...
        if( port==-1 ) {
            try {
                awaitThread = Thread.currentThread();
                while(!stopAwait) {
                    try {
                        Thread.sleep( 10000 );
                    } catch( InterruptedException ex ) {
                        // continue and check the flag
                    }
                }
            } finally {
                awaitThread = null;
            }
            return;
        }
        // ...
    }
SpringBoot tomcat是如何监听请求和分发请求的
    Tomcat 监听和分发请求的过程就是通过 Servlet 容器来实现的。当客户端发送一个请求时,Tomcat 接收到请求并交给 Servlet 容器处理,Servlet 容器根据请求的 URL 找到对应的 Servlet,并调用其 service() 方法来处理请求,最后将响应发送给客户端。这个过程是基于 Servlet 规范的,Spring Boot 只是将其封装并提供了方便的配置和使用方式。

java Servlet原理,为什么启动了一个Servlet容器之后可以一直运行
    Servlet容器能够一直运行是因为它使用了线程池和自动管理Servlet的生命周期等机制,能够持续地处理客户端请求,并通过错误处理机制确保容器的稳定运行。

Servlet是如何监听网络端口的
    ervlet通过在web.xml文件中配置监听器(listener)来监听网络端口。监听器是一种特殊的Servlet,它可以监听特定的事件,并在事件发生时执行相应的操作。

Servlet使用什么监听网络端口的,和Nio的socket监听端口有什么区别
    Servlet使用的是阻塞式的ServerSocket,而NIO的SocketChannel是非阻塞的,并且具有更好的扩展性和性能。

SpringBoot和Vertx哪个框架比较好
   SpringBoot基于阻塞式的Servlet,而Vertx基于netty的nio及事件驱动,一个是阻塞一个是非阻塞,如果你需要快速启动一个传统的Java应用程序并享受Spring生态系统的好处,那么选择SpringBoot是一个不错的选择。
   如果你需要构建高性能、可伸缩和响应式的应用程序,那么选择Vert.x可能更合适。
SpringBoot 接收到controller流程:
    前端请求-->容器处理(如tomcat版本到达了8使用的是java NIO异步处理)-->分发给Servlet处理(同步)
Vertx
    前端请求-->Netty使用reactor分发请求-->基于Router事件驱动找到对应的Hander处理(包括请求分发到哪个controller整个流程都是异步非阻塞的)
Play Framework
    前端请求-->Netty使用reactor分发请求-->采用传统的MVC(Model-View-Controller)架构模式,没有基于事件驱动

controller原理:
    单例,DispatcherServlet,统一在finishBeanFactoryInitialization加载
    
SpringCloud:https://www.bilibili.com/video/BV18E411x7eT

SpringCloud和SpringCloud Alibaba
                    SpringCloud                                SpringCloudAlibaba
注册中心            Eureka                                    nacos
消息中间件            无(第三方替代方案:rabbitmq)            RocketMQ
分布式事务解决方案    无(第三方替代方案:2pc)                    Seata
分布式调度服务        无(第三方替代方案:xxl-job)                Alibaba Cloud SchedulerX
短信平台            无    Alibaba Cloud                          SMS
分布式配置中心        SpringCloudConfig                        nacos
熔断降级            Hystrix                                    Sentinel
存储                无                                        Alibaba Cloud OSS
服务调用            feign                                    feign
负载均衡            Ribbon                                    Ribbon

SpringCloud通信为什么使用http协议,调用风格为什么使用restful
    http是最通用的协议,可以很好地解决跨语言跨平台兼容性。比如Dubbo协议,无法简单地实现跨语言,只适用于dubbo。
    http交互只是协议比较重,但不会慢太多,追求单机极致性能的确可以考虑换协议。目前微服务的一个重要理念就是水平扩展,慢这个问题通过业务设计还有多实例部署可以很好地提速。
    http协议很成熟,并且特点为大家所熟知:1.支持客户/服务器模式。2.简单快速。3.灵活。4.无连接。5.无状态。
    其实就是一个速度与开发成本还有兼容性相互妥协兼顾的结果

注册中心与服务发现:Eureka zk consul、nacos、Consul、etcd、redis、mysql

消息中间件:Kafka、RabbitMQ、RocketMQ、ActiveMQ

配置中心:SpringCloudConfig、Nacos、Apollo

RPC服务调用:Dubbo、SpringCloud rpc(基于feign的http调用)、Thrift、Motan

spring-cloud调用服务两种方式:Ribbon+RestTemplate、Feign

Eureka和Zookeeper注册中心的区别
    SpringCloud和Dubbo都支持多种注册中心,不过目前主流来看SpringCloud用Eureka较多,Dubbo则以Zookeeper为主。两者存在较大的差异:
    从集群设计来看:Eureka集群各节点平等,没有主从关系,因此可能出现数据不一致情况;ZK为了满足一致性,必须包含主从关系,一主多从。集群无主时,不对外提供服务
    CAP原则来看:Eureka满足AP原则,为了保证整个服务可用性,牺牲了集群数据的一致性;而Zookeeper满足CP原则,为了保证各节点数据一致性,牺牲了整个服务的可用性。
    服务拉取方式来看:Eureka采用的是服务主动拉取策略,消费者按照固定频率(默认30秒)去Eureka拉取服务并缓存在本地;ZK中的消费者首次启动到ZK订阅自己需要的服务信息,并缓存在本地。然后监听服务列表变化,以后服务变更ZK会推送给消费者。

一致性算法的定义
    一致性就是数据保持一致,在分布式系统中,可以理解为多个节点中数据的值是一致的。
    
CP:服务可以不能用,但必须要保证数据的一致性。
AP:数据可以短暂不一致,但最终是需要一致的,无论如何都要保证服务的可用。
    
ZK为什么是满足CP:
    使用了强一致性算法ZAB

nacos同时支持cp和AP
    CP:使用了raft算法,用于选举leader,数据的一致是同步的
    AP:也使用raft算法,用于选举leader,但是使用了Distro 协议,用于实现多节点数据同步和负载均衡。在 Distro 协议中,每个节点都可以作为数据源和数据同步的目标,通过数据的分发和同步,保证各个节点之间的数据一致性。,数据的一致是异步的

Nacos:nacos支持两种方式的注册中心,持久化和非持久化存储服务信息。
    (默认)非持久直接存储在nacos服务节点的内存中,并且服务节点间采用去中心化的思想,服务节点采用hash分片存储注册信息
    持久化使用Raft协议选举master节点,同样采用过半机制将数据存储在leader节点上,每个节点都存储了数据的副本

强一致性算法ZAB、raft与paxos的原理和区别:
    Raft和ZAB算法是对Paxos算法的简化和改进,raft和zab leader选举都是采用过半机制,集群写入数据都是采用两阶段提交
    ZAB:
        leader选举:zxid+myid
        数据写入两阶段提交
        角色:    
            Leader:负责请求转发、读、写
            Follower:只负责读,参与投票和选举,写请求转发给leader
            Observer:没有投票权、选举权,不参与选举阶段,广播阶段,只参与读不参与写
                注:observer存在的意义:增加系统吞吐量、伸缩性
        ZAB四种状态
            LOOKING:当节点认为集群中没有Leader,服务器会进入LOOKING状态,目的是为了查找或者选举Leader
            FOLLOWING:follower角色
            LEADING:leader角色
            OBSERVING:observer角色
    raft:
        leader选举:term号+时间段的超时时间,当每个term号相同时,谁先超时谁就赢得选举
        数据写入两阶段提交
        角色:
            Leader:负责发起心跳,响应客户端,创建日志,同步日志。
            Candidate:Leader选举过程中的临时角色,由 Follower 转化而来,发起投票参与竞选。
            Follower:接受Leader的心跳和日志同步数据,投票给 Candidate
        broadcastTime(心跳时间) << electionTimeout(选举超时时间) << MTBF(单台机器健康时间)
    
限流:信号量、redis、令牌算法、漏桶算法、滑动时间窗口算法、分布式限流Sentinel、nginx、tomcat、Hystrix

RPC-dubbo
rpc原理:
服务消费端(client)以本地调用的方式调用远程服务; 
客户端 Stub(client stub) 接收到调用后负责将方法、参数等组装成能够进行网络传输的消息体(序列化):RpcRequest; 
客户端 Stub(client stub) 找到远程服务的地址,并将消息发送到服务提供端; 
服务端 Stub(桩)收到消息将消息反序列化为Java对象: RpcRequest; 
服务端 Stub(桩)根据RpcRequest中的类、方法、方法参数等信息调用本地的方法; 
服务端 Stub(桩)得到方法执行结果并将组装成能够进行网络传输的消息体:RpcResponse(序列化)发送至消费方; 
客户端 Stub(client stub)接收到消息并将消息反序列化为Java对象:RpcResponse ,这样也就得到了最终结果。over!

dubbo作用:提供服务自动注册、自动发现,
算法:基于软负载均衡算法
dubbo组件:服务消费者(Consumer)、注册中心(Registry)、监控中心(Monitor)【累计调用次数和调用时间】、Container(服务运行容器,负责加载、运行服务【提供者】)

Dubbo支持的通信协议:dubbo、rmi、hessian(dubbo默认)、http、webservice、thrift、redis

Dubbo支持的序列化协议:
    dubbo-hessian-lite 是官方 hessian 的一个 Apache Dubbo 私有版本
    Apache Avro™ 是一个数据序列化系统。
    Jdk 序列化
    JSON - fastjson:一个Java版本内的 JSON 解析器/生成器
    FST: 一个快速的Java序列化工具
    Kryo是一个Java版本的快速有效的二进制对象序列化框架

dubbo:采用单一长连接和NIO异步通讯,适合于小数据量大并发的服务调用,以及服务消费者机器数远大于服务提供者机器数的情况

软负载均衡和硬负载均衡:
    在系统服务器上安装相应负载均衡软件,进行相关的配置,达到均衡负载的目的,如nginx
    直接在服务器和外部网络间安装硬负载均衡设备,如F5
    
负载均衡算法:轮询法、加权轮询法、加权随机法(dubbo默认负载均衡算法)、最小连接数法、随机法、源地址哈希法

dubbo工作流程:
    服务提供者启动start,然后注册resgister服务,消费者订阅subscribe服务,如果没有订阅到服务,会不断尝试订阅。新的服务注册到注册中心以后,注册中心会将服务notify到
    消费者,monitor监控consumer和provider调用次数
    
dubbo源码及原理实现:
    1.spring启动中,spring基于dubbo.jar的Meta-inf/spring.handlers配置,找到对应的命名空间,回调DubboNamespaceHandler命名空间处理类,调用init方法获取解析器,通过DubboBeanDefinitionParser解析器
    将xml标签解析成bean对象。如会将service解析为ServiceBean
    2.其中serviceBean对象继承了ServiceConfig类,实现了InitializingBean, DisposableBean, ApplicationContextAware, ApplicationListener, BeanNameAware,覆写了ApplicationListener中的onApplicationEvent,该方法在spring bean
    初始化完成之后会调用,其中就有一个export方法,该方法实现了服务的注册功能,将Bean对象转会为url格式,将所以Bean属性转成url的参数,基于不同的protocal进行服务暴露和引用
    3.ReferenceBean通过FactoryBean来实现消费服务去注册中心拉取相应的服务
    4.dubbo网络传输调用底层基于Socket或者NETTY实现
    
SPI的具体原理:将接口的实现类放在配置文件中,我们在程序运行过程中读取配置文件,通过反射加载实现类。

如何实现自定义负载均衡策略:实现 LoadBalance 接口,将这个实现类的路径写入到resources 目录下的 META-INF/dubbo/org.apache.dubbo.rpc.cluster.LoadBalance文件中即可

dubbo微内核架构:说白点就是支持插件

注册中心和监控中心都宕机的话,服务都会挂掉吗?
    不会。两者都宕机也不影响已运行的提供者和消费者,消费者在本地缓存了提供者列表。注册中心和监控中心都是可选的,服务消费者可以直连服务提供者
    
既有 HTTP ,为啥用 RPC 进行服务调用
    RPC 只是一种设计,http是一种协议,使用不同的协议一般也是为了适应不同的场景

NETTY:
Reactor线程模型:单线程模型、多线程模型、主从多线程模型
https://www.zhihu.com/question/26943938
https://www.cnblogs.com/dawen/archive/2011/05/18/2050358.html
Reactor模式
    同步非阻塞模型
    单 Reactor 单线程
        可以看到进程里有 Reactor、Acceptor、Handler 这三个对象:
            Reactor 对象的作用是监听和分发事件;
            Acceptor 对象的作用是获取连接;
            Handler 对象的作用是处理业务;
        单 Reactor 单线程方案:
            Reactor 对象通过 select (IO 多路复用接口) 监听事件,收到事件后通过 dispatch 进行分发,具体分发给 Acceptor 对象还是 Handler 对象,还要看收到的事件类型;
            如果是连接建立的事件,则交由 Acceptor 对象进行处理,Acceptor 对象会通过 accept 方法 获取连接,并创建一个 Handler 对象来处理后续的响应事件;如果不是连接建立事件, 则交由当前连接对应的 Handler 对象来进行响应;Handler 对象通过 read -> 业务处理 -> send 的流程来完成完整的业务流程。
        缺点
        第一个缺点,因为只有一个进程,无法充分利用 多核 CPU 的性能;
        第二个缺点,Handler 对象在业务处理时,整个进程是无法处理其他连接的事件的,如果业务处理耗时比较长,那么就造成响应的延迟;
        所以,单 Reactor 单进程的方案不适用计算机密集型的场景,只适用于业务处理非常快速的场景
        案例实现:
        Redis 是由 C 语言实现的,它采用的正是「单 Reactor 单进程」的方案,因为 Redis 业务处理主要是在内存中完成,操作的速度是很快的,性能瓶颈不在 CPU 上,所以 Redis 对于命令的处理是单进程的方案
    单Reactor多线程
        方案实现流程:
            Reactor 对象通过 select (IO 多路复用接口) 监听事件,收到事件后通过 dispatch 进行分发,具体分发给 Acceptor 对象还是 Handler 对象,还要看收到的事件类型;
            如果是连接建立的事件,则交由 Acceptor 对象进行处理,Acceptor 对象会通过 accept 方法 获取连接,并创建一个 Handler 对象来处理后续的响应事件;如果不是连接建立事件, 则交由当前连接对应的 Handler 对象来进行响应;
        上面的三个步骤和单 Reactor 单线程方案是一样的,接下来的步骤就开始不一样了:
            Handler 对象不再负责业务处理,只负责数据的接收和发送,Handler 对象通过 read 读取到数据后,会将数据发给子线程里的 Processor 对象进行业务处理;
            子线程里的 Processor 对象就进行业务处理,处理完后,将结果发给主线程中的 Handler 对象,接着由 Handler 通过 send 方法将响应结果发送给 client
        优缺点:
            单 Reator 多线程的方案优势在于能够充分利用多核 CPU 的能,那既然引入多线程,那么自然就带来了多线程竞争资源的问题。
            要避免多线程由于竞争共享资源而导致数据错乱的问题,就需要在操作共享资源前加上互斥锁,以保证任意时间里只有一个线程在操作共享资源,待该线程操作完释放互斥锁后,其他线程才有机会操作共享数据。
            事实上,单 Reactor 多进程相比单 Reactor 单线程实现起来很麻烦,主要因为要考虑子进程 <-> 父进程的双向通信,并且父进程还得知道子进程要将数据发送给哪个客户端。
            而多线程间可以共享数据,虽然要额外考虑并发问题,但是这远比进程间通信的复杂度低得多,因此实际应用中也看不到单 Reactor 多进程的模式。
    多Reactor多线程 
        方案详细说明如下:
            主线程中的 MainReactor 对象通过 select 监控连接建立事件,收到事件后通过 Acceptor 对象中的 accept 获取连接,将新的连接分配给某个子线程;
            子线程中的 SubReactor 对象将 MainReactor 对象分配的连接加入 select 继续进行监听,并创建一个 Handler 用于处理连接的响应事件。
            如果有新的事件发生时,SubReactor 对象会调用当前连接对应的 Handler 对象来进行响应。
            Handler 对象通过 read -> 业务处理 -> send 的流程来完成完整的业务流程。
Proactor
    异步非阻塞模式
实际案例:
    redis:单 Reactor 单线程
    Netty:都支持
    kafka:主从Reactor多线程模型
    
NETTY线程模型:单线程模型、多线程模型、主从多线程模型

Mybatis 
Dao接口里的方法,参数不同时,方法能重载吗
    不能
Dao接口的工作原理是 JDK 动态代理,Mybatis 运行时会使用 JDK 动态代理为 Dao 接口生成代理 proxy 对象,代理对象 mapperproxy 会拦截接口方法,转而执行MappedStatement所代表的 sql,然后将 sql 执行结果返回

Mybatis是如何进行分页的?分页插件的原理是什么?
    Mybatis 使用 RowBounds 对象进行分页,它是针对ResultSet结果集执行的内存分页

Mybatis 是否支持延迟加载?如果支持,它的实现原理是什么?
    Mybatis 仅支持 association 关联对象和 collection 关联集合对象的延迟加载,association 指的就是一对一,collection 指的就是一对多查询。在 Mybatis 配置文件中,可以配置是否启用延迟加载 lazyLoadingEnabled=true|false。
    使用 CGLIB 创建目标对象的代理对象,当调用目标方法时,进入拦截器方法,比如调用 a.getB().getName(),拦截器 invoke()方法发现 a.getB()是 null 值,那么就会单独发送事先保存好的查询关联 B 对象的 sql,把 B 查询上来,然后调用 a.setB(b),于是 a 的对象 b 属性就有值了,接着完成 a.getB().getName()方法的调用。这就是延迟加载的基本原理。
Mybatis 中如何执行批处理?
    使用 BatchExecutor 完成批处理
Mybatis 中如何指定使用哪一种 Executor 执行器?
    在 Mybatis 配置文件中,可以指定默认的 ExecutorType 执行器类型,也可以手动给 DefaultSqlSessionFactory 的创建 SqlSession 的方法传递 ExecutorType 类型参数
Mybatis 是否可以映射 Enum 枚举类?
    Mybatis 可以映射枚举类,不单可以映射枚举类,Mybatis 可以映射任何对象到表的一列上。映射方式为自定义一个 TypeHandler,实现 TypeHandler 的 setParameter()和 getResult()接口方法。
为什么说 Mybatis 是半自动 ORM 映射工具?它与全自动的区别在哪里?
    Hibernate 属于全自动 ORM 映射工具,使用 Hibernate 查询关联对象或者关联集合对象时,可以根据对象关系模型直接获取,所以它是全自动的。而 Mybatis 在查询关联对象或关联集合对象时,需要手动编写 sql 来完成,所以,称之为半自动 ORM 映射工具。

Mybatis一级缓存和二级缓存
    一级缓存(默认开启):它指的是Mybatis中sqlSession对象的缓存,当调用SqlSession的修改、添加、删除、commit(),close等方法时,就会清空一级缓存    
    二级缓存(手动开启):他指的是Mybatis中SqlSessionFactory对象的缓存,由同一个SqlSessionFactory对象创建的SqlSession共享其缓存,但是其中缓存的是数据而不是对象,所以从二级缓存再次查询出的结果对象与第一次存入的对象是不一样的。
    流程:(mybatis的的一级缓存是SqlSession级别的缓存,一级缓存缓存的是对象,当SqlSession提交、关闭以及其他的更新数据库的操作发生后,一级缓存就会清空。二级缓存是SqlSessionFactory级别的缓存,同一个SqlSessionFactory产生的SqlSession
    都共享一个二级缓存,二级缓存中存储的是数据,当命中二级缓存时,通过存储的数据构造对象返回。查询数据的时候,查询的流程是二级缓存>一级缓存>数据库。)

    Spring如何解决Mybatis一级缓存脏读问题(跨服务时,一级缓存在A服务还在,在B服务已经被修改提交之后)
    Spring在SqlSessionInterceptor的invoke调用时,直接如果尚未开启事务,则sqlSession直接提交,则一级缓存就失效了
    
一级缓存周期:
    一级缓存的作用范围是会话Session,如果会话一旦消亡那么一级缓存也跟着消亡
    
mybatis缓存数据持久化:
    定时将缓存的数据刷新至硬盘,也可以使用第三方的集成工具如:Redis,来进行缓存的存储
    
MyBatis二级缓存自动清理
    cache中配置溢出淘汰算法,默认是LRU,所以二级缓存也使用LinkedHashMap

二级缓存如何保证线程安全(为什么需要序列化):
    在执行过程中,因为二级缓存是可跨线程的,所以可能导致两个线程拿到同一个对象。所以需要进行序列化,那么如果线程1修改了缓存中的数据,线程2去获取缓存中的数据,在线程2获取数据后,线程1进行回滚,从而导致了线程2的脏读。
    为了解决这个问题,所以必须把这个对象序列化,这样两个线程拿到的对象是一样的,但是序列化的ID是不一样的,这样就可以很好的解决这个问题

mybatis使用流程:
    1.读取Configuration配置(包括environments配置和mapper配置) 2.通过SqlSessionFactoryBuilder的builder方法获取sqlSessionFactory 3.通过factory工厂得到sqlSession 4.通过sqlSession执行sql语句
    
mybatis(SqlSessionFactoryBuilder)源码配置解析过程(https://www.cnblogs.com/zhjh256/p/8512392.html)
    1.读取文件,将文件解析成流(字节流/字符流都可以),进入new XMLConfigBuilder(),创建XPathParser,其中默认了文档节点解析器为XMLMapperEntityResolver,通过XPathParser调用createDocument方法,通过SAX使用默认的文档解析器
    将typeHandlers、mappers、environments、plugins标签等解析成一个document,调用XMLConfigBuilder.parse(),所有节点(typeHandlers、mappers、environments、plugins等)都经过evaluate()计算,最后包装成Configuration,通过
    build方法new DefaultSqlSessionFactory(config)得到SqlSessionFactory
    
mybatis设计模式
    策略、工厂、享元(pool连接池)、代理模式(mapper接口的代理,属于jdk的动态代理)、装饰器(cache实现)、模板方法模式(不同的执行器实现不同的方法,里面采用的是模板方法模式)等
    
mybatis mapper接口的动态代理原理:
    通过MapperFactoryBean实现FactoryBean的getObject方法,然后通过MapperProxyFactory,进行jdk的代理生成代理对象
mybatis重要configration节点:properties, settings, typeAliases, typeHandlers, objectFactory, objectWrapperFactory, reflectorFactory, plugins, environments, databaseIdProvider, mappers

mybatis通过什么解析Sql的CRUD语句
    mybatis通过语言驱动器LanguageDriver 其实叫做解析器进行sql的解析,可以实现自己的语言驱动器进行sql的解析,mybatis默认的语言驱动器为XMLLanguageDriver
    
静态标签解析和动态标签解析的两种方式:
    静态标签使用RawSqlSource进行解析,动态标签使用DynamicSqlSource(比如使用了where、choose等标签或者使用${}的叫动态sql)
    
SqlSource:
    SqlSource是XML文件或者注解方法中映射语句的实现时表示,通过SqlSourceBuilder.parse()方法创建,SqlSourceBuilder中符号解析器将mybatis中的查询参数#{}转换为?,并记录了参数的顺序。它只有一个方法getBoundSql用于获取映射语句对象的各个组成部分

SqlSource实现有如下:
    StaticSqlSource:最终静态SQL语句的封装,其他类型的SqlSource最终都委托给StaticSqlSource。
    RawSqlSource:原始静态SQL语句的封装,在加载时就已经确定了SQL语句,没有、等动态标签和${} SQL拼接,比动态SQL语句要快,因为不需要运行时解析SQL节点。
    DynamicSqlSource:动态SQL语句的封装,在运行时需要根据参数处理、等标签或者${} SQL拼接之后才能生成最后要执行的静态SQL语句。
    ProviderSqlSource:当SQL语句通过指定的类和方法获取时(使用@XXXProvider注解),需要使用本类,它会通过反射调用相应的方法得到SQL语句。
    
例如DynamicSqlSource源码:
    public BoundSql getBoundSql(Object parameterObject) {
    DynamicContext context = new DynamicContext(configuration, parameterObject);
    //SqlNode的对应实现解析对应的Node
    rootSqlNode.apply(context);
    SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);  //获取SqlSourceBuilder
    Class parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
    SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());  //通过parse返回一个StaticSqlSource,其中的sql语句就是在这里生成的
    BoundSql boundSql = sqlSource.getBoundSql(parameterObject);  //通过StaticSqlSource的getBoundSql构建一个BoundSql对象返回
    for (Map.Entry entry : context.getBindings().entrySet()) {
      boundSql.setAdditionalParameter(entry.getKey(), entry.getValue());
    }
    return boundSql;
  }
    
SqlNode:
    SqlNode接口主要用来处理CRUD节点下的各类动态标签
    
mybatis如何解析和执行mapper文件中的sql:(关键名词:SqlSessionFactoryBuilder、XMLConfigBuilder、XMLMapperBuilder、document、resultMap、XMLLanguageDriver、sqlSource、sqlNode)
    首先在SqlSessionFactoryBuilder执行build方法,对相关的配置文件进行解析,其中就有对mapper配置文件的解析,首先mybatis会使用SAX将文件解析成document,通过解析document中的每个节点得到configration,其中对mapper文件的处理在XMLMapperBuilder中
    XMLMapperBuilder.parse方法进行mapper文件的标签的解析,包括resultMap和动态sql,其中resultmap的解析结果会放到resultMap的对象中,包括一些列名,type信息,对sql的解析使用对应的语言驱动器创建sqlSource,采用的是sqlSource进行标签解析(其中分为
    静态的sql标签解析器和动态的标签解析器),在后续进行select、update调用时使用sqlNode(ChooseSqlNode、ForEachSqlNode、IfSqlNode、TrimSqlNode、SetSqlNode(委托给了TrimSqlNode)等)去处理CRUD节点下的动态标签,在使用的时候,sqlSource会将#{}替换成?
    
ResultMap对象:
    ResultMap类维护了每个标签中的详细信息,比如id映射、构造器映射、属性映射以及完整的映射列表、是否有嵌套的resultMap、是否有鉴别器、是否有嵌套查询

Configuration(配置及sql容器)
    Configuration是mybatis所有配置以及mapper文件的元数据容器。无论是解析mapper文件还是运行时执行SQL语句,都需要依赖与mybatis的环境和配置信息

mybatis缓存:
    只要实现org.apache.ibatis.cache.Cache接口的任何类都可以当做缓存,cacheKey=ID + offset + limit + sql + parameterValues + environmentId
    
Mybatis一级缓存和二级缓存
    一级缓存(默认开启):它指的是Mybatis中sqlSession对象的缓存,当调用SqlSession的修改、添加、删除、commit(),close等方法时,就会清空一级缓存    
    二级缓存(手动开启):他指的是Mybatis中SqlSessionFactory对象的缓存,由同一个SqlSessionFactory对象创建的SqlSession共享其缓存,但是其中缓存的是数据而不是对象,所以从二级缓存再次查询出得结果的对象与第一次存入的对象是不一样的。
    流程:(mybatis的的一级缓存是SqlSession级别的缓存,一级缓存缓存的是对象,当SqlSession提交、关闭以及其他的更新数据库的操作发生后,一级缓存就会清空。二级缓存是SqlSessionFactory级别的缓存,同一个SqlSessionFactory产生的SqlSession
    都共享一个二级缓存,二级缓存中存储的是数据,当命中二级缓存时,通过存储的数据构造对象返回。查询数据的时候,查询的流程是二级缓存>一级缓存>数据库。)

Mybatis缓存失效条件
    对于一级缓存,commit/rollback都会清空一级缓存。
    对于二级缓存,DML操作或者显示设置语句层面的flushCache属性都会使得二级缓存失效。

mybatis sql方法执行过程:
    前置:SqlSessionFactoryBuilder的build步骤解析配置文件,通过SqlSessionFactory获取openSession,根据sql语句及Xml配置,sql可以分为以下两种
    第一种,调用org.apache.ibatis.session.SqlSession的crud方法比如selectList/selectOne传递完整的语句id直接执行;
  第二种,先调用SqlSession的getMapper()方法得到mapper接口的一个实现
    (第一种比较少用,且第二种是基于第一种实现基础上,直接看第二种)
    调用SqlSession.getMapper()方法,实际调用的是configuration.getMapper()到MapperRegistry.getMapper得到Mapper的实现代理,代理实际又通过代理类MapperProxy实现jdk动态代理得到,其中invoke方法会调用mapperMethod.execute方法,
    接下来的execute都是根据第一种的调用方式基础上做的
    select查询:
        查询语句通过返回类型是void/many/map/one/cursor判断走不同的方法,以selectOne为例,selectOne以selectList为基础,判断selectList返回的是一个还是多个,多个的话会抛出异常,其中selectList处理为
            1.获取MappedStatement 2.通过sqlSource的sqlNode解析sql获取BoundSql(需要执行的sql),查询缓存中是否存在,如果存在已执行的接口,直接返回,否则查询数据库 3.首先获取对应的执行器Executor,设置对应的超时时间等,
            4.获取语句处理器,并调用参数处理器处理封装参数  5.调用doQuery方法进行查询 6.将查询接口通过结果集处理器将JDBC查询接口映射到Java对象或者返回Map(如调用selectMap方法)
    update更新(insert和delete实际也是调用update):
        1.调用update先设置一个dirty=true,默认自动提交和回滚 2.通过configuration获取MappedStatement 3.清空本地缓存 4.调用doUpdate方法获取对应的语句处理器 5.调用JDBC对应update的方法execute()(此处可以忽略,太细了。和select不同的是,他会在excute调用前后生成
        前置id和后置id)

MappedStatement定义:
    mapper文件或者mapper接口中每个映射语句都对应一个MappedStatement实例,它包含了所有运行时需要的信息比如结果映射、参数映射、是否需要刷新缓存等

XML映射文件关系:
    Mybatis 将所有 Xml 配置信息都封装到 All-In-One 重量级对象 Configuration 内部。在 Xml 映射文件中,标签会被解析为 ParameterMap 对象,其每个子元素会被解析为 ParameterMapping 对象。
    标签会被解析为 ResultMap 对象,其每个子元素会被解析为 ResultMapping 对象。每一个