JDBC事务

1. JDBC访问流程

  • 注册驱动
  • 通过DriverManager获取连接
  • 获取statement
    • PreparedStatement是预编译的statement防治SQL注入
  • 给 PreparedStatement设置参数
  • 执行插入或者修改或者查询
    • 查询会返回resultSet对象,然后再去解析resultSet对象

2. ThreadLocal底层原理

ThreadLocal的作用
threadLocal代表当前线程,我们可以往当前线程里面绑定相应的值,后面可以通过当前线程获取到这个值

为什么ThreadLocal能够把值绑定到当前线程?
threadLocal会为我们每一个线程去创建一个map集合,当调用set方法时,首先会获取到当前线程的引用,然后获取到当前线程对应的map集合,往map集合当中去设置值;当调用get方法时,会获取到当前线程对应的map值,然后在获取到对应的值

在这里插入图片描述

3. 聊聊mybatis

mybatis当中有mapper动态代理开发,实际上当我们编写mapper接口后,mybatis会用接口生成一个代理对象,当mybatis接口对象的任何方法调用之后都会去触发动态代理的invoke方法,如果说我们用了XML,那么会根据XML标签去解析执行到底是插入还是查询等,获取SQL,如果说使用的注解,那么会解析方法上面的注解,得到SQL,实际上底层也是通过JDBC执行SQL

如果是查询,执行完查询之后他会把返回结果用反射进行封装

4. ConcurrentHashMap

HashTable加了锁,当多个线程来操作的时候现在同一时刻只要一个线程进来

1.8之前
ConcurrentHashMap会采用分段锁,16个小段,分别上锁和释放锁;

5. 什么是IOC

把创建对象的交给Spring容器
XML当中配置了Bean标签,Spring底层会去解析XML,解析到Id、class,然后利用反射创建对象放到map容器,key就是id或者name,value就是反射创建的这个对象;获取就直接通过map获取

6. 什么是DI

在创建对象的时候给Bean赋值

在XML当中Bean标签当中配置了property标签,在这个property标签配置了值是哪些,然后Spring底层利用反射去解析XML之后给对应的属性赋值操作

7. Spring整合servlet原理

当每次调用service的时候都会去创建spring容器?怎么解决
spring容器只能创建一次,可以放到ServletContext对象,域对象,整个容器只有一个

当Spring和Servlet整合的时候首先需要在web.xml当中去配置监听器,这个监听器就会去监听到servletContext的创建,因为要创建Spring容器,所以当我们创建Spring容器的时候实际上会要指定Spring配置文件,然后我们会在web.xml当中配置全局的初始化参数去指定Spring配置文件的位置;然后在监听器里面我们可以通过servletContext来获取到配置文件的位置;在监听器当中去创建spring容器,创建完成之后我们需要把spring容器放到servletContext域当中,后面的servlet如果要来使用spring容器,可以直接从servletContext域当中获取到spring容器

8. Spring父子容器

Spring的父容器是配置监听器的并且把它放到ServletContext当中,子容器就是前端控制器,那么他在什么时候创建呢?再DispatcherServlet继承FrameworkServlet再继承HttpServletBean里面有个init方法,该方法里面有个initServletBean方法,该方法被FrameworkServlet所重写,然后初始化创建子容器initWebApplicationContext,该方法里面首先会获取到父容器的引用,然后创建子容器的时候把父容器传递进去,记住父容器的引用(为了就是可以在子容器当中拿到父容器的bean,可以注入父容器)。

子容器会在监听器之后创建,然后会配置项目启动的时候创建即DispatcherServlet创建的时候创建,如果不配置则默认再第一次访问的时候创建,但是对第一个用户体验太差!

父容器不能注入子容器的bean
子容器可以注入父容器的bean

8. 类加载器

就是将class文件加载到方法区里面class区
启动类加载器
在这里插入图片描述
扩展类加载器
在这里插入图片描述
系统类加载器

9. 处理器适配器是如何获取参数并封装的

处理器适配器会调用适配器controller当中的方法来执行,首先会通过反射来拿到这个方法,也可以通过反射来获取到参数名称,直接通过request.getParameter来取出参数,在执行方法的时候在通过反射的invoke把参数传递过来

10. 怎么配置注解事务

  • 首先配置事务管理器
  • 然后配置开启注解事务<tx:annotation-driven >
  • 然后在需要开启事务的类上加上@Transaction注解
  • 实际上还是可以配置传播行为和隔离级别,也可以在方法上配置可以覆盖类上的配置

11. SpringAOP的理解

  1. aop是一种思想,纵向重复代码横向抽取。Springaop底层采用动态代理来实现的,如果实现了接口spring会采用jdk动态代理,如果没有实现接口会采用cglib动态代理,采用子类继承的方案。

  2. 使用AOP在Spring里面使用首先去编写通知,前置通知、异常通知、环绕通知、最终通知,然后配置切入点表达式,把通知和切入点表达式结合起来配置一个切面,针对需要拦截的方法生成代理对象

  3. springaop可以控制事务,控制事务有两种方式;第一种是手动控制(编程事务)、第二种AOP自动的声明事务,不管用哪种方式去控制都需要配置事务管理器,因为持久层的技术有各种,所有spring为我们提供了platformtransactionmanager接口,在这个接口里面有获取事务、提交事务、回滚事务的方法

  4. 在开启事务的事务需要传递事务的定义信息TransactionDefinition,这个定义信息的隔离级别是什么,事务的传播行为(决定了事务在业务方法之间如何传递的问题),设置了定义信息就可以获取开启事务会返回事务的状态,在这个事务状态里面封装了是否提交、是否回滚,紧接着就可以拿到commit、rollback回滚的操作了;这样就可以控制细粒度事务,但是它也会造成在每个service方法都会编写这个事务,很麻烦

  5. 此时可以利用AOP的思想环绕通知,我们在执行业务代码之前来开启事务,执行完了之后就提交事务,异常那么在catch里面回滚,这样就利用了AOP的思想把事务封装起来了

  6. spring提供了两种声明式事务,第一种使用XML方式,首先配置事务管理器,第二配置事务的通知(attributes属性)去设置这些方法,哪些方法开头的,隔离界别、是否只读、传播行为等,然后配置织入(通知和被代理对象结合起来创建代理对象的过程,配置切入点表达式,配置切面)

  7. 第二种还可以配置注解的方式,第一配置事务管理器,第二是开启事务注解驱动,接下来我们只需要在类上标志Transaction注解就可以,如果我们在方法上配置了那么就可以覆盖在类上的配置

在这里插入图片描述

  • 编程式事务是细粒度的事务,Spring其实也为我们提供了一个工具类叫做TransactionTemplate,该工具类要注入事务管理器
  • 程序员需要在业务方法里面去注入这个工具类,然后调用excute方法,里面传入一个回调的方法,因为Spring不知道我们具体要调用什么方法,因此采用了回调的思想
  • 在这个excute方法的底层首先会是开启事务、执行业务代码、提交事务、回滚事务等
  • 在这个excute传入的回调方法可以是有返回值的,也可以是没有返回值的
  • 没有返回值的这个类是一个抽象类,它实现了有返回的这个接口,重写了doTransaction的这个方法,并且调用了自己没有返回的这个抽象方法,并且这个抽象方法是被我们程序员所重写
  • 这个过程就行浏览器发送请求到tomcat,调用了service方法,service方法会调用get或者post等方法

12. 为什么二级缓存和三级换存是HashMap

因为二级缓存和三级缓存是成对出现的,集二级缓存里面是三级缓存生成的,所有两个map里面不能同时存在bean,所有应该保证原子性,就算他们是concurrentHashMap也得加锁,所有反正都要加锁,就没有必要是concurrentHashMap了

13.spring怎么实例化bean的

spring实例化bean有声明周期,首先实例化对象,然后推断构造方法,拿到构造方法反射创建出来

14. 如何解决循环依赖的

spring进行扫描-反射后封装成beanDefinition对象-放入beanDefinitionMap-遍历map-验证(是否单例、是否延迟加载、是否抽象)-推断构造方法-准备开始进行实例-去单例池中查,没有-去二级缓存中找,没有提前暴露-生成一个objectFactory对象暴露到二级缓存中-属性注入,发现依赖Y-此时Y开始它的生命周期直到属性注入,发现依赖X-X又走一遍生命周期,当走到去二级缓存中找的时候找到了-往Y中注入X的objectFactory对象-完成循环依赖。

15. MySQL执行流程

from
on
join
where
group by
having
select
distinct
union
order by

16. 分布式Session问题

nginx对web服务器进行了集群,但是登陆状态存储在服务器进程内,nginx转发到不同的服务器,造成了重复的登陆问题
在这里插入图片描述

17. 如何解决分布式Session

  • 为了保证每一次客户端都是访问都是同一个服务器,ip hash的负载均衡策略;缺点:这台服务器宕机了,这个客户的登陆信息就没有了
  • tomcat可以设置session共享;缺点:每台服务器都有登陆的信息数据有冗余其次就是只能是tomcat服务器之间,服务器不能混用了

18. 正向代理

客户端行为,例如浏览器要访问某某网站,因为防火墙的原因访问不了,因此需要使用代理服务器,然后代理服务器帮我们访问,访问结果转发给客户端

也可以是客户端为了加速某某网站,然后配置了代理服务器来加快它访问,实际上对应游戏网站根本就不知道使用了代理服务器

19. 反向代理

服务器行为,nginx处理不了动态资源的请求,然后nginx把请求转发给tomcat,处理完毕指挥相应给nginx,再由nginx相应给浏览器,浏览器也不知道是访问的是tomcat,以为是访问的nginx

20. JWT

客户端发起登陆请求,服务器端生成JWT令牌,首先服务端这边有个secret密钥,然后服务端首先会把头部生成加密算法使用base64进行编码,载荷存放登陆信息以及权限的信息,也会使用base64进行编码,紧接着会把(头部base64+载荷base64+密钥)在通过头部声明的加密算法进行加密就生成签名,然后把JWT办法给客户端,客户端后面会把JWT携带给服务器,

服务器在验签的时候:

  • 首先会获取JWT令牌
  • 服务器获取头部和载荷,并解析头部声明的加密算法
  • 服务器会使用(头部base64+载荷base64+密钥)在用头部声明的加密算法再次进行加密,然后把加密后的信息和签名的信息进行比较,如果一致代表签名合法,没有被篡改,如果不一致,不认
  • 一致则获取载荷当中的数据,载荷数据是合法的,没有被篡改

验证过程
服务器端会编写一个拦截器主要是判断客户端是否发送了token以及判断token是否是合法的,如果是合法的就会获取载荷当中的内容并且绑定用户信息到当前的线程,当访问后端的接口的时候会从当前线程当中获取用户权限的列表,

21. Java的可见性

首先Java的内存模型当中有主内存,每个线程都有本地内存,本地内存会拷贝主内存的副本,当线程执行的时候实际上是改变本地内存的值,本地内存改了之后会刷新到主内存,另外一个线程会到主内存当中读取到值过后更新到本地内存,相互之间通过主内存来通讯

如果加了volatile关键字会解决可见性的问题,因为可以确保本地内存的值改变了之后会即使的刷新到主内存当中,另外一个线程可以及时的去读取数据

重排序
计算机在执行程序时,为了提高性能,编译器和处理器的常常会对指令做重排,volatile实现禁止指令重排优化,从而避免多线程环境下程序出现乱序执行的现象

21. 什么是CAS

并发原语,判断内存当中某个位置的值是否是期望值,如果是更新值,这个过程是原子的
在多线程环境下的i++不会用synchronized,因为太重了,我们会用原子类,AntomicInteger,底层是CAS,conpareAndSet比较并交换,假设主物理内存是5,线程1拷贝主物理内存的值到工作内存,做一系列之后比较期望主物理内存值是5,修改为2019,那么就改变,如果主物理内存修改不是5就不交换,类似于git的版本提交号(真实值和期望值相同修改成功,真实值和期望值不相同修改失败)

22. CAS底层原理

atomicInteger.getAndIncrement()凭什么不加synchronized凭什么能够保证原子性,它的底层是个unsafe类是CAS的核心类,来自rt.jar包下面,由于Java无法访问底层系统的就是native修饰的方法,Unsafe相当于一个后门,可以直接操作特定内存的数据,Unsafe类所以的方法都是native修饰的,也就是说直接调用底层操作系统资源执行相应任务,并且原语的执行必须是连续,不能够中断,

22. 阻塞队列和非阻塞队列

阻塞队列和非阻塞队列都实现了队列的结构

阻塞队列:因为队列有容量所以在入队的时候,可以设置阻塞的时间,如果队列满了,就阻塞,如果有位置就放置进去,出队的时候如果队列是空的也可以进行阻塞的状态

非阻塞队列:代表高性能的队列,不用设置队列的大小可以直接放置进去,不会阻塞

23. 线程池工作原理

  • 首先会判断线程池当中的线程数是否小于核心池设置的大小
  • 如果小则会创建一个新的线程来执行,如果这个线程执行完成任务,不会关闭,他会尝试从阻塞队列当中获取新的任务,如果没有任务则进入阻塞状态
  • 如果三个线程都在忙碌,然后又有新的任务,创建新的线程数量以及超过核心池的大小,不会再去创建新的线程,把这个任务放到阻塞队列
  • 如果现在有其他的任务,仍然继续往阻塞队列当中放任务,如果放的时候阻塞队列满了,这个时候如果我们配置的最大线程数大于核心线程池的大小,就会创建新的线程执行任务
  • 这个新的线程执行完毕之后不会立即关闭,他也会尝试从阻塞队列当中获取任务来执行
  • 如果超过了设置时间还没有新的任务来执行,那么这些额外创建的线程就会全部关闭
  • 如果所有的线程都在执行任务,并且达到了最大线程数,并且阻塞队列也满了,这个时候会执行拒绝策略

在这里插入图片描述

23. 如何合理的配置线程池

配置线程池的大小可根据CPU密集型任务,IO密集型任务

  • CPU密集型:尽量使用较小的线程池,一般为CPU核心数+1。
    • 因为CPU密集型任务使得CPU使用率很高,若开过多的线程数,只能增加上下文切换的次数,因此会带来额外的开销。
  • IO密集型:可以使用稍大的线程池,一般为2*CPU核心数+1。
    • 因为IO操作不占用CPU,不要让CPU闲下来,应加大线程数量,因此可以让CPU在等待IO的时候去处理别的任务,充分利用CPU时间。

24. Callable

  • 编写类继承Thread类
  • 编写了实现Runable接口
  • 编写类实现Callable接口
    • 代表当前任务带有返回的结果
    • 而且Callable必须交给线程池来执行,交给线程池之后,会在线程池执行完成之后才会获取到结果
    • 因此会返回Future对象,代表会在未来才能获取到结果
    • 现在在创建线程池的主线程里面通过Future.get()是个阻塞的方法
    • 阻塞的方法有两种情况:
      • 执行玩有结果之后直接获取到结果
      • 没有结果则进行阻塞状态

接着聊线程池的核心配置参数、线程池底层原理,拒绝策略,自定义拒绝策略、合理配置线程池

25. 什么是垃圾回收机制

jvm会不定时的回收不可达的对象

什么是不可达的对象
对象没有被引用或者没有存活

垃圾回收机制是Java的核心,也是必不可少的,Java有一套自己的垃圾清理机制,开发人员无需手动的清理。

26. 堆内存

堆内存分为新生代和老年代,新生代占1/3,老年代占2/3

新生代分为Eden区和s0和s1区
Eden占8/10,两个survivor各占1/10

27. 如何判断对象是否存活

引用计数法

默认年龄为0岁,每个对象都有一个年龄,如果小于或者等于15岁,存放在新生代当中,如果大于15岁就会存放到老年代当中

GC线程会不定时的回收,如果判断对象被引用的话则+1岁,如果没有就-1岁
如果年龄为0岁,会被垃圾回收机制认为是个不可达的对象会被回收

循环引用问题
在这里插入图片描述
会造成内存泄漏
根搜索算法
什么是根

(1). 虚拟机栈(栈帧中的局部变量区,也叫做局部变量表)中引用的对象。
(2). 方法区中的类静态属性引用的对象。
(3). 方法区中常量引用的对象。
(4). 本地方法栈中JNI(Native方法)引用的对象。
在这里插入图片描述

28. 复制算法

Eden里面的对象用可达性分析算法发现存活则放入幸存区,然后幸存区满了又用可达性分析算法标记哪些是存活的哪些是没有存活的,会把存活的对象复制到另外一个幸存区里面,把刚才那个幸存区直接清理掉

适用于新生代

优点:可以解决内存碎片化问题、快速、清理干净
缺点:浪费内存

s0和s1一定有一个为空,因为为了下一次的复制

29. 标记整理算法

适用于老年代
在这里插入图片描述
优点:解决内存碎片化问题
缺点:更新引用

30. 标记清除算法

发生在老年代
一个是标记,另一个就是清除。标记就是根据特定的算法(如:引用计数算法,可达性分析算法等)标出内存中哪些对象可以回收,哪些对象还要继续用。标记指示回收,那就直接收掉;标记指示对象还能用,那就原地不动留下。

缺点:产生内存碎片化问题,因为内存不够连贯

31. Minor GC和FullGC区别

Minor GC:指发生在新生代的垃圾回收动作,因为Java对象大多都具备朝生夕灭的特性,一般回收机制也比较快

MajorGC:指发生在老年代的GC,出现了MajorGC经常会伴随至少一次的MinorGC

31. 永久代

JDK1.8的时候把永久代替换为元空间是直接使用本地内存(方法区里面的东西直接使用本地内存,不在走JVM来分配了)

如果JVM分配了500M空间,JDK1.8之前永久代可能只能分配100M
现在JVM分配了500M空间,现在的元空间可以直接使用物理内存

之前给JVM分配空间可能预估不足,永久代可能会产生内存溢出

元空间和永久代区别元空间和永久代类似,但是元空间并不在虚拟机内,而使用本地内存,理论上取决于32位/64位操作系统的大小

在1.8之前方法区放到永久代里面,1.8之后把永久代去除了,方法区

32. 内存溢出和内存泄露区别

Java内存泄漏就是没有及时清理内存垃圾,导致系统无法再给你提供内存资源(内存资源耗尽);

而Java内存溢出就是你要求分配的内存超出了系统能给你的,系统不能满足需求,于是产生溢出。

内存溢出,这个好理解,说明存储空间不够大。就像倒水倒多了,从杯子上面溢出了来了一样。

内存泄漏,原理是,使用过的内存空间没有被及时释放,长时间占用内存,最终导致内存空间不足,而出现内存泄漏。

33. 引用

强引用
以前我们使用的大部分引用实际上都是强引用,这是使用最普遍的引用。如果一个对象具有强引用,那就类似于必不可少的生活用品,垃圾回收器绝不会回收它。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足问题。

弱引用
就类似于生活当中可有可无的商品,与软引用相比,在垃圾回收的时候,一旦发现弱引用的对象,不管内存空间足够与否,都会回收它的内存;由于垃圾回收器是优先级很低的线程,因此不一定会很快的发现弱引用对象

弱引用可以和引用队列结合使用,如果弱引用被垃圾回收器回收,Java虚拟机会把这个弱引用加入到与之关联的引用队列当中

34. ThreadLocal内存泄漏

ThreadLocal的key是弱引用,key为使用弱引用的ThreadLocal实例,value为线程变量的副本。弱引用不管内存是否够都会回收,但是value是强引用,而这个value会在调研set(),get(),remove()清除掉,因此避免内存泄漏问题,虽然这个key是弱引用但是只是多了一层屏障,为了确保value被删除,因此每次使用完ThreadLocal之后我们要调用remove()方法

tomcat的底层是线程池,也就以为线程不会关闭,当有一个请求过来之后会拿一个线程来执行,然后这个线程会对于有个threadLocalMap,并且使用了当前线程ThreadLocal,我们往里面绑定一个用户,然后在过滤器里面,我们一定要调用remove方法,这样才能让value清除

ThreadLocalMap使用ThreadLocal的弱引用作为key,如果一个ThreadLocal不存在外部强引用时,Key(ThreadLocal)势必会被GC回收,这样就会导致ThreadLocalMap中key为null, 而value还存在着强引用,只有thead线程退出以后,value的强引用链条才会断掉。

但如果当前线程再迟迟不结束的话,这些key为null的Entry的value就会一直存在一条强引用链:

Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value

永远无法回收,造成内存泄漏。

那为什么使用弱引用而不是强引用??

我们看看Key使用的

key 使用强引用

当ThreadLocalMap的key为强引用回收ThreadLocal时,因为ThreadLocalMap还持有ThreadLocal的强引用,如果没有手动删除,ThreadLocal不会被回收,导致Entry内存泄漏。

key 使用弱引用

当ThreadLocalMap的key为弱引用回收ThreadLocal时,由于ThreadLocalMap持有ThreadLocal的弱引用,即使没有手动删除,ThreadLocal也会被回收。当key为null,在下一次ThreadLocalMap调用set(),get(),remove()方法的时候会被清除value值。

35. 垃圾回收器

串行
只有一个线程,执行垃圾回收时程序停止的时间比较长,垃圾回收时,只有一个线程在工作, 并且java应用中的所有线程都要暂停,等待垃圾回收的完成,作用于新生代,采用复制的垃圾回收算法

并行
线程执行垃圾回收适合于吞吐量的系统,回收时系统会停止运行,就是把单线程改为多线程的垃圾回收,作用于新生代,采用复制的垃圾回收算法,JDK8默认使用的垃圾回收器

parNew就是Serial的多线程版本,新生代并行,老年代串行,老年代使用的是标记压缩算法

CMS
是一款并发的标记清除算法垃圾回收器,该回收器针对老年代,是一款为了获取最短回收停顿时间的垃圾回收器,停顿时间短,在进行垃圾回收的同时,系统仍然能运行

  • 初始标记:仅仅标记GCRoots能直接关联到的对象,速度快
  • 并发标记:追踪引用链的过程,可以和用户线程并发的执行
  • 重新标记:修正并发标记阶段用户线程继续运行而导致标记发生
  • 并发清除:清除标记可以回收的对象,可以和用户线程并发执行

由于整个过程耗时最长的并发标记和并发清除都可以和用户线程一起工作,所以总体上来看,CMS收集器的内存回收过程和用户线程是并发执行的。

缺点:

  • 无法处理浮动垃圾:
  • 垃圾回收算法导致内存碎片:

35. G1

之前不管串行回收还是并行回收,新生代和老年代会使用两种收集器,
G1把调优的参数给简化了,只需要设置开启G1设置,取消了物理划分,只是逻辑上的划分,有Eden,Survivor,Old,还要巨大对象区域,这个区域会动态划分,不要设置新生代和老年代的大小,G1收集器在运行的时候会调整新生代和老年代的大小
我们只需要设置最大的堆内存大小,紧接着我们只需要调试程序的停顿时间即可

36. JVM参数调优

  • 设置堆的初始大小和最大大小,通常把最大、初始大小设置为相同的值。减少GC的次数
    • -Xms:设置堆的初始化大小
    • -Xmx:设置堆的最大大小
  • 设置年轻代中Eden区和两个Survivor区的大小比例。该值如果不设置,则默认比例为8:1:1。Java官方通过增大Eden区的大小,来减少YGC发生的次数
    • -XX:SurvivorRatio
  • 年轻代和老年代默认比例为1:2。可以通过调整二者空间大小比率来设置两者的大小。
  • -XX:newSize 设置年轻代的初始大小
  • -XX:MaxNewSize 设置年轻代的最大大小, 初始大小和最大大小两个值通常相同
  • 一般一天超过一次FullGC就是有问题,首先通过工具查看是否出现内存泄露,如果出现内存泄露则调整代码,没有的话则调整JVM参数
  • 系统CPU持续飙高的话,首先先排查代码问题,如果代码没问题,则咨询运维或者云服务器供应商
  • 如果数据查询性能很低下的话,如果系统并发量并没有多少,则应更加关注数据库的相关问题
  • 如果服务器配置还不错,JDK8开始尽量使用G1或者新生代和老年代组合使用并行垃圾回收器

36. Tomcat调优

如果是原生的tomcat我们只需要在catalina设置加上JVM参数即可,在server.xml当中配置线程池的参数

如果是springboot项目然后在使用-jar 的时候配置JVM参数,在yaml或者properties当中配置线程池的参数

37. 数据库三大范式

  • 确保每列保证原子性(每一列只要单一的值不能够再拆分)
  • 确保每列的值都与主键相关
  • 每一个表都不包含其他表已经包含的非主键信息

37. 数据库使用优化思路

应急调优的思路:

针对突然的业务办理卡顿,无法进行正常的业务处理!需要立马解决的场景!

  1. show processlist(查看连接session状态)查看是否连接过多
  2. explain(分析查询计划),show index from tableName(分析索引)
  3. show status like ‘%lock%’; 查询锁状态

常规调优的思路:

针对业务周期性的卡顿,例如在每天10-11点业务特别慢,但是还能够使用,过了这段时间就好了。

  1. 开启慢查询日志,运行一天;(set global slow_query_log=1;)
  2. 设置慢查询日志的超时时间 (set global long_query_time=1;)
  3. 查看slowlog,分析slowlog,分析出查询慢的语句。
  4. 按照一定优先级,进行一个一个的排查所有慢语句。
  5. 分析top sql,进行explain调试,查看语句执行时间。
  6. 调整索引或语句本身。

38. 复合索引在什么情况下失效

复合索引是要遵守最左匹配原则,如果现在有name,age,address 那么他会根据name建个索引,根据name,age建个,根据name,age,address建个,他不会用后面两个字段来单独建个索引

39. B树

B树的一个节点可以用于超过2个子节点的二叉查找树,并且一个节点可以存储多个值,那么B树就可以相对于平衡二叉树降低树的高度,那么就可以减少磁盘的IO操作

缺点:仍然需要回旋

40. B+树

解决的范围查找的问题、减少IO查询的操作

用B树的特点降低了树的高度,减少IO查询的操作
非叶子节点只有key,是用来帮我们查找叶子节点的,并且叶子节点会使用链表来连接起来也就是方便我们来范围查找

缺点就是有冗余的数据占用内存

41. 索引的面试题

如果现在使用hash的数据结构来实现索引,这个时候我们可以针对某个字段计算出哈希值得到下标就找到对应的值,缺点就是不支持范围查找,因为底层数据结构是散列的,无法进行比较大小

平衡二叉树的话是右边比它大左边比它小,可以降低磁盘IO的次数,但是平衡二叉树的树的高度比较高,一个节点最多只有两个子节点,并且如果是范围查找它的回旋效率比较低

B树一个节点可以用于超过2个子节点的二叉查找树,而且一个节点可以有多个值,所以树的高度变低了,但是缺点是如果是范围查找仍然是回旋效率比较低

B+树是在B树的特点上多了叶子节点,通过非叶子节点找到叶子节点,非叶子节点只有key没有value,而叶子节点既有key也有value,因为叶子节点会通过链表连接起来,这是可以通过链表很方便的实现范围查找,并且也有B树的特点降低了树的高度

42. SQL优化技巧

  • 使用group by 分组查询是,默认分组后,还会排序,可能会降低速度,在group by 后面增加 order by null 就可以防止排序.
  • 有些情况下,可以使用连接来替代子查询。因为使用join,MySQL不需要在内存中创建临时表。
  • 对查询进行优化,要尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引
  • 应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描
  • 唯一性太差的字段不适合单独创建索引,即使频繁
  • 频繁更新字段,也不要定义索引。

43. 聚簇索引和非聚簇索引区别

索引的底层数据结果是B+树,那么它的非叶子节点只有key,而叶子节点存储key和value

聚簇索引也成为主键索引,实际上对应的叶子节点上key对应的是主键id,值是主键对应这一行的数据
非聚簇索引也成为二级索引,他在叶子节点上存储的只是主键的id,所以如果是通过非聚簇索引来查询的话首先是在它的叶子节点上招待主键的id,然后通过id到主键索引的叶子节点上找到对应这一行的数据

44. synchronized的底层原理

它是一个JVM级别的锁,是自动上锁和解锁,首先每一个对象关联一个monitor监视的C++对象,该monitor有两个属性:一个是owner记录用于锁的线程,一个是记录锁的次数;然后它在底层加锁的时候底层有一个monitorenter指令加锁,monitorexit解锁,当在monitorenter进行加锁的时候,多个线程会争先恐后的去monitor对象里面去设置值,如果能从默认值0改为1那么就代表获取到这个锁资源,另外的线程就会陷入阻塞状态,直到执行完成之后把1改为0,其他线程又可以去设置值

如果出现的锁嵌套就是可重入锁,那么这个监视器就会从1加到2加到3等等,里面的锁执行完毕之后会依次的去减为0

monitorenter小结:synchronized的锁对象会关联一个monitor,这个monitor不是我们主动创建的,是JVM的线程执行到同步代码块,发现锁对象没有monitor就会创建monitor,monitor的内部有两个重要的成员变量,一个是owner:由于这把锁的线程,一个是recursions会记录线程拥有锁的次数,当一个线程拥有monitor的时候其他线程陷入阻塞
当执行到monitorexit时,recursions会释放锁,当计数器减到0时会释放锁并且会唤醒cxp或者EntryList当中的节点

核心的成员变量:

  • owner:记录拥有该monitor的线程
  • waitset:记录处于wait状态的线程
  • EntryList:等了一轮也没有抢到锁就放到
  • cxq:第一轮没有抢到锁

在这里插入图片描述

44. monitor竞争

当t1线程和t2线程同时来抢占锁的时候

  • 通过CAS尝试把monitor的owner字段设置为当前线程
  • 如果设置之前的owner指向当前线程,说明当前线程再次进入monitor,即重入锁,执行recursions ++,记录重入的次数
  • 如果获取锁失败,则等待锁的释放。

44. Synchronized和Lock的区别

  • synchronized是关键字,lock是一个接口
  • synchronized会自动释放锁即便出现异常也是,lock是手动释放锁
  • synchronized是不可中断的,而调用lock方法是不可中断的,而调用trylock我们可以尝试获取锁,并且给的时间,时间到了就终端
  • 通过lock可以知道知道有没有拿到锁,调用trylock返回值,true表示拿到锁,false没有拿到锁,synchronized不能
  • synchronized能锁住方法和代码块,lock只能锁代码块
  • lock有个reentryreadlock可以多个线程来读,提高读的效率,只有一个线程来写
  • synchronized是非公平锁,唤醒的时候不是按照先来后到来唤醒的,ReentrantLocko以控制是否是公平锁.

45. 为什么在controller当中成员变量可以注入request对象

浏览器每次发送请求到服务器都会创建request对象,而controller要交给springmvc容器来管理,所以当springmvc初始化的时候就会被创建,当在成员变量来注入request对象是有些问题,因为controller是单例的,request每次访问都会被创建,解决就是在controller当中的成员变量创建的是代理对象,在每次访问过来的时候tomcat会创建一个代表请求的request对象和代表响应的response对象,传递service方法springmvc会把request绑定到当前线程!!!;成员变量就是从当前线程获取到的相应的数据

在这里插入图片描述

46. ThreadLocal作用

threadLocal可以把当前的请求绑定到当前线程 例如我们可以在controller成员变量的位置注入request,为什么没问题 controller提前就创建好了,而request每次请求都会创建,因此成员变量创建的是一个代理对象,每次请求过来的时候会把request绑定到当前线程,而controller成员变量的request会从当前线程当中去获取request

47. FactoryBean和BeanFactory的区别

ApplicationContext继承了BeanFactory接口,ApplicationContext包含了BeanFactory的所有功能,同时还进行了更多扩展

FactoryBean是个工厂bean,使用了简单工厂模式,是一个可以生产对象的工厂bean 由spring管理后,spring自动调用getObject方法获取具体需要创建的对象交给spring管理

FactoryBean适合于整合第三方框架的时候,因为第三方框架可能会交给Spring容器来进行管理,并且创建第三方框架的时候创建过程十分的繁琐,这个时候第三方框架可以专门写一个FactoryBean实现FactoryBean接口,例如mybatis当中的sqlsessionFactoryBean来交给spring容器来管理,spring会自动的调用getObject方法把创建的对象交给spring容器来管理

如果想要将动态代理创建的对象交给spring容器管理,那么就需要实现FactoryBean接口,把动态代理创建的对象放到getObject方法里面,例如Mapper只有接口没有实现类,然后把这个工厂Bean交给Spring容器来管理,spring会自动调用getObject方法把代理对象放到spring容器里面

BeanFactory是个bean工厂,是一个spring工厂的顶级接口,就是个ioc容器 管理bean的

/**
* Created by zxd on 2021/7/23 15:21
*/
public interface UserMapper {
	public void hello();
}
@Component
public class UserMapperFactoryBean implements FactoryBean<UserMapper> {
    @Override
    public UserMapper getObject() throws Exception {
        Class[] clazz={UserMapper.class};
        return (UserMapper) Proxy.newProxyInstance(UserMapperFactoryBean.class.getClassLoader(), clazz, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("invoke被调用了");
                if(method.getName().equals("hello")){
                    System.out.println("奥利给你好...");
                    return null;
                }else if(method.getName().equals("toString")){
                    System.out.println("toString被调用了...");
                    return "toString xxx";
                }else{
                    return null;
                }

            }
        });
    }

    @Override
    public Class<?> getObjectType() {
        return UserMapper.class;
    }

    @Override
    public boolean isSingleton() {
        return true;
    }
}

在这里插入图片描述
"null"是idea会自动的去调用代理对象的toString方法

现在有一个问题?不可能每次创建一个mapper就要对应的去创建一个FactoryBean,因此需要修改

在这里插入图片描述
增加了一个有参的构造方法,与此同时也要相应的在bean的定义信息加上有参的参数,

AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
applicationContext.register(ChenApplication.class);

AbstractBeanDefinition abstractBeanDefinition = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
abstractBeanDefinition.setBeanClass(ChenFactoryBean.class);
abstractBeanDefinition.getConstructorArgumentValues().addGenericArgumentValue(UserMapper.class);
applicationContext.registerBeanDefinition("user",abstractBeanDefinition);
applicationContext.refresh();

但是不可能写在启动类当中,因此需要实现ImportBeanDefinitionRegistrar然后再启动类当中导入该接口,spring才能知道调用这个方法
在这里插入图片描述
但是对应mybatis的作者来说,他并不知道我们有哪些mapper,所以不能写死,因此需要扫描路径
可以参照Spring的扫描,但是Spring在接口当中添加@Component是不起作用的,因为不可能实例化接口,但是对应mybatis来说,是只要接口的,与spring的逻辑正好相反

继承spring的扫描的逻辑
在这里插入图片描述
传入这个package,返回加了@Component类的个数
在spring扫描的逻辑是当他扫描之后其实会根据这个类生成BeanDefinition

在这里插入图片描述
重写了spring的逻辑,把接口作为候选的Component

47. BeanDefinitionBuilder

核心代码:

AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
applicationContext.register(ChenApplication.class);
applicationContext.refresh();
AbstractBeanDefinition abstractBeanDefinition = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
abstractBeanDefinition.setBeanClass(ChenFactoryBean.class);
abstractBeanDefinition.getConstructorArgumentValues().addGenericArgumentValue(UserMapper.class);
applicationContext.registerBeanDefinition("user",abstractBeanDefinition);

在这里插入图片描述

user是获取的getObject方法的返回
&user是获取的是LuBanBeanFactory的Bean

48. @EnableAutoConfiguration

它的下面有两个注解,一个是@AutoConfigurationPackage自动扫描包,利用Registrar给容器中导入一系列组件将指定的一个包下的所有的组件导入进来、主程序所在的包下面。

还有就是@Import(AutoConfigurationImportSelector.class)

  • 利用getAutoConfigurationEntry(annotationMetadata);给容器中批量导入一些组件
  • 调用List configurations = getCandidateConfigurations
  • 利用工厂加载 Map<String, List> loadSpringFactories
  • 从META-INF/spring.factories位置来加载一个文件。
    • 默认扫描我们当前系统里面所有META-INF/spring.factories位置的文件
      在这里插入图片描述
      在这里插入图片描述
      虽然我们127个场景的所有自动配置启动的时候默认全部加载,按照条件装配规则(@Conditional),最终会按需配置。

49. SpringBoot自动装配得过程

  • SpringBoot在启动得时候会自动加载自动装配得jar包,autoconfigure.jar
  • 然后加载这个jar包下META-INF文件下得spring.factories文件
  • 读取这个配置文件配置了哪些自动装配的类,然后这个时候会自动加载这个配置文件中的类
  • 每一个自动装配的类上都有一个自动装配的注解,这个注解叫做ConditionalOnClass,检测当前项目是否导入了对应的starter,检测当前的自动装配类所需要的这些类在当前项目是否存在,如果存在自动装配就会生效
  • 我们这个配置类上还要configuration注解,代表当前类是个配置类,可以写Java配置

50. Innodb和MyISAM的区别

Innodb

  • innodb存储引擎提供了提交、回滚、崩溃恢复能力的事务安全
  • 提拱了对数据库事务ACID的支持,实现了四种隔离级别
  • InnoDB引擎是行锁,粒度更小,所以写操作不会锁定全表,在并发较高时,使用InnoDB会提升效率。即存在大量UPDATE/INSERT操作时,效率较高

使用场景

  • 经常UPDETE/INSERT的表
  • 支持事务只能选择innodb
  • 可以从灾难中恢复
  • 外键约束、列属性AUTO_INCREMENT支持

MyISAM

  • 不支持事务、不支持外键
  • 每个MyISAM在存储成3个文件
    • frm:表结构等信息
    • MYD:存储数据
    • MYI:存储索引

使用场景

  • 经常SELECT的表,插入不频繁,查询非常频繁
  • 不支持事务

51. DCL单例模式

双端检索机制不一定线程安全,因为可能会出现指令重排的情况,加入volatile可以禁止指令重排
原因在于当某一个线程执行到第一次检测,读取到instance不为null,instance的引用对象可能没有初始化完成
instance=new SingleDemo()分为三步

  • 开辟内存
  • 初始化
  • 引用 instance!=nill
    步骤二和步骤三不存在依赖关系,所有存在指令重排变为步骤1,3,2
    但是得到对象还没有初始化完成最后得到null

第一个if语句,用来确认调用getInstance()时instance是否为空,如果不为空即已经创建,则直接返回,如果为空,那么就需要创建实例,于是进入synchronized同步块。

synchronized加类锁,确保同时只有一个线程能进入,进入以后进行第二次判断,是因为,

对于首个拿锁者,它的时段instance肯定为null,那么进入new Singleton()对象创建,

而在首个拿锁者的创建对象期间,可能有其他线程同步调用getInstance(),那么它们也会通过if进入到同步块试图拿锁然后阻塞。

这样的话,当首个拿锁者完成了对象创建,之后的线程都不会通过第一个if了,而这期间阻塞的线程开始唤醒,它们则需要靠第二个if语句来避免再次创建对象。

52. 什么是线程池

因为不可能每次请求过来的时候都来创建线程,这就会导致性能不高。不管用tomcat还是jetty都是在多线程的环境,那么就会维护一个线程池,提前把线程创建好,当请求来的时候就拿一个线程,当线程执行完之后把线程还到线程池当中

  • 降低资源的消耗
  • 提高响应的速度
  • 提高线程的可管理性

53. 线程池的核心类

核心池大小、最大线程池大小、最大等待时间、最大等待时间单位、拒绝策略

53. 线程池的工作原理

  • 首先判断线程池的线程数是否小于核心池设置的大小
  • 如果小的话会创建一个新的线程来执行,如果这个线程执行完任务不会关闭,他会尝试从阻塞队列当中获取新的任务来执行,如果没有任务则会进入阻塞的状态
  • 如果核心线程数都在忙碌,然后又有新的任务,创建新的线程数量已经超过了核心池的大小,不会再去创建新的任务,把这个任务放入阻塞队列
  • 如果现在有其他的任务,仍然继续往阻塞队列当中放任务,如果阻塞队列满了,这个时候如果我们配置了最大线程数大于核心池的大小,就会创建新的线程来执行任务
  • 这个新的任务执行完成之后不会马上关闭,他会尝试从阻塞队列当中获取任务来执行
  • 如果超过了设置时间还没有新的任务来执行,那么这些额外创建的线程就会关闭
  • 如果所有的线程都在执行任务,并且达到了最大线程数,并且阻塞队列也满了就会执行拒绝策略

53. 如果线程池内存队列满了会怎样

如果使用的是有界队列还可以创建额外的线程数来执行,但是如果超过了最大线程数则会执行拒绝策略

如果现在有这个需求,因为默认的拒绝策略都抛异常、以及丢弃新的,或者是老的,但是我们希望不希望丢弃而是并发量不高的时候来执行

我们首先可以自定义拒绝策略,把任务持久化到硬盘或者是数据库当中,等空闲的时候从磁盘当中或者是数据库当中读取来执行(使用定时器)

53. 如何自定义拒绝策略

我们只需要编写一个类来实现RejectedExecutionHandler接口
在这里插入图片描述

53. 如果线上的机器突然宕机,挤压在队列当中里的任务怎么办

提交任务当线程池之前,我们可以往数据库当中插入这个任务的信息,可以给一个字段,记录状态,未提交、已提交、已完成,之后执行完了过后改变状态就可以了

系统重启,启动线程读取数据库中未完成的任务,然后再把任务重新提交到线程池中去执行。

54. 自定义数据绑定

在springmvc的底层运用了ServletModelAttributeMethodProcessor 这个参数处理器来进行封装,他在他的底层运用了WebDataBinder数据绑定器,这个数据绑定器会将请求的所有数据跟指定的JavaBean进行绑定,如何进行绑定的?这个绑定器里边有ConversionService类型转换服务,在里边注册了非常多的converters,这些converters能将String转成各种的类型,例如这些converters将String转为integer,再将integer转到JavaBean当中,最终完成JavaBean的数据跟请求的数据进行绑定

55. 线程当中如何保证线程安全的问题

使用Synchronized或者Lock锁或者是JUC原子类CAS无锁机制

56. 数据库的优化

可能会使用反三范式来设计设计方法来设计,就可以适当的添加冗余字段来增加查询的效率

57. servlet的生命周期

servlet是跑在Tomcat服务器上的
Tomcat服务器创建了servlet容器

当第一次访问的时候

  • 创建了servlet对象
  • 调用了init方法 初始化servlet
  • 调用了service方法 处理请求

当我们停止了服务器就调用了destory销毁方法

58. JDK动态代理

因为创建代理对象的时候我们传入了接口,所以我们的代理对象会具备相同的方法,但是JDK动态代理并不知道代理对象要干什么事情,因此只要程序员自己来写所以编写一个类来实现InvocationHandler接口,当代理对象的方法被调用之后都会触发invoke方法

59. 本地方法栈

Java的底层JVM基本都是C或者是C++写的,不管本地方法是什么实现的总归有些代码是要执行的,那么就需要内存空间,那么就分配到本地方法栈当中

60. 栈

当主线程来运行main方法,Java虚拟机会给这个线程分配个专属的线程栈内存空间,只要运行到方法就会在他自己线程栈上给这个方法分配个内存空间用来存放方法内部的局部变量,这块内存就叫栈帧方法内存空间

61. 程序计数器

记住下一条JVM内存的地址

62. 方法区内存

被虚拟机加载的类信息、常量、静态常量等,是线程共享的
运行时常量池也是方法区的一部分,Class文件除了字段、方法、版本接口等描述信息还有一项就是常量池,用于存放编译期间生成的各种字面量的符合的引用,这部分内容将在加载后进入方法区的运行时常量池中存放。

61. 远程调用过程

去创建一个service接口,给这个service生成代理对象,然后当调用接口的方法的时候会触发invoke方法,在invoke方法里面发起socket请求,去请求服务的提供方,让服务的提供方那边去执行本地执行,执行完之后把结果返回给调用方

62. IO和NIO的区别

dobbo的底层用到了netty,netty的底层用到了NIO,dubbo的底层不用传统的IO而用的是NIO,因为传统的IO是一个线程只能处理一个socket的请求,性能比较低而且是一个阻塞的IO,NIO是一个非阻塞的IO,可以实现一个线程去处理多个socket的请求并且是带有缓存的还要选择器,性能比较高

63. Mongodb的使用场景

MongoDb 可以用来存储一些不重要的数据,并且不能够设计到复杂的表操作,因为他是不支持事务的

  • 物流场景,使用 MongoDB 存储订单信息,订单状态在运送过程中会不断更新,以 MongoDB 内嵌数组的形式来存储,一次查询就能将订单所有的变更读取出来。
  • 视频直播,使用 MongoDB 存储用户信息、礼物信息等

64. zk的监听器

zk是有监听器的,它允许某个客户端针对某个子节点的变化进行监听,不管是新增还是减少子节点的数据的变化都能够进行相应的监听,并且在监听的时候使用的是长连接
在这里插入图片描述
如果创建临时节点的客户端断开与zk的连接,那么这个临时节点超时过后会自动的删除

65. HttpClient实现RPC

浏览器访问服务器的过程

  • 打开浏览器
  • 输入网址
  • 访问
  • 结果

使用HttpClient访问WEB服务的过程

  • 创建客户端,相当于打开浏览器
  • 创建请求地址,相当于输入网址
  • 发起请求,相当于访问网站
  • 处理相应结果,相当于浏览器显示结果
public class TestHttpClient {
    public static void main(String[] args) throws IOException {
        testGetNoParams();
    }

    /**
     *
     */
    public static void testGetNoParams() throws IOException {
        // 创建客户端对象
        HttpClient client = HttpClients.createDefault();
        // 创建请求地址
        HttpGet httpGet = new HttpGet("http://localhost/test");
        // 发送请求 ,接收相应对象
        HttpResponse response = client.execute(httpGet);
        // 响应体和响应头,都是封装的HTTP协议数据。直接使用可能有乱码或解析错误
        HttpEntity entity = response.getEntity();
        // 通过HTTP实体工具类,转换响应体数据。使用的字符集是UTF-8
        String responseString = EntityUtils.toString(entity,"UTF-8");
        System.out.println("服务器相应数据:"+responseString);
        client=null;
    }
}

有参数的POST传递参数
在这里插入图片描述
在这里插入图片描述

66 .HTTP协议和RPC协议对比

RPC适用于公司的内部服务调用,传输效率高,性能消耗低

1、传输协议

  • RPC:可以基于HTTP协议,也可以基于TCP协议
  • HTTP:基于HTTP协议

2、性能消耗

  • RPC:可以基于thrift实现高效的二进制传输
  • HTTP:大部分是基于json实现的,字节大小和序列化耗时都比thrift要更消耗性能

3、负载均衡

  • RPC:基本自带了负载均衡策略
  • HTTP:需要配置Nginx、HAProxy配置

67. CPU100%的排查方案

  • 首先使用top -c 查看CPU的使用率
  • 进一步使用top -Hp 14724定位该进程内所有的线程使用情况
  • 将查出的线程id转换为16进制aa
  • 然后用jstack查出该线程具体的堆栈

68. Redis和memcached的区别

现在一般都会采用redis来实现缓存存储

  • reids支持更丰富的数据类型,不仅仅支持string还支持list、set、hash、zset等,而memcached只支持string数据类型
  • redis支持数据的持久化,可以将内存的数据持久化保存到磁盘当中,而memcached将数据保存到内存当中,断电及失
  • memcached是多线程,非阻塞IO复用的网络模型,而redis是单线程的多路IO复用模型

69. 缓存的双写不一致的问题

我们项目分为mobile和管理端admin,我们在mobile端做了缓存,但是需要解决缓存的双写不一致问题。比如酒店详情缓存,我们在修改了酒店详情缓存之后,mobile缓存的还是老的数据,出现了缓存不一致的问题
我们可以在admin管理端修改酒店的时候发送一个HttpClient请求来删除mobile端的缓存当中的数据

admin消息生产者需要把消息发送给mq中间指定的通道,因为我们可以修改房型,也可以修改房间,所以类型不同那么通道就不同,就像快递员需要把快递分发到不同的传送带上,消费者接收哪一个通道的消息

现在有个问题

  • 在项目当中是在用户访问的时候做的缓存,假设系统很多酒店,在项目启动的时候,有许多请求访问不同的酒店那么就都会从缓存当中找,没有,再到数据库,从而打垮数据库,因此在启动项目的时候把缓存做好
  • 还有项目耦合度太高,如果删除缓存失败,自身修改酒店也会不成功
  • 通知失败没有重试机制

70. redis如何解决分布式session的问题

因为现在做了集群的部署,而session是保存到内存当中的,集群项目的的内存是独立的,造成重复登陆的问题

如果本来是传统的项目改动不想太大

  • 那么就可以使用nginx负载均衡策略IP绑定,这一台服务器的用户信息可能丢失
  • tomcat的session共享,会造成数据冗余,而且tomcat和jetty不能混用共享
  • Jwt是在客户端解决的,当登陆之后会把登陆信息发给客户端,让客户端来存储,不管访问哪台服务器直接进行解密就可以,但是可能会被暴力破解的风险
  • 单点登录的redis解决方案:当登陆过来之后redis会存储登陆的信息,key存储token,value存储用户信息,然后会把token发给客户端,当下一次访问的时候携带token令牌去找redis

71. 为什么需要单点登陆系统

就是在微服务当中,每个模块都抽离开来,不可能说每个模块都要进行登陆,所以会把登陆的系统单独抽离开,客户端登陆的时候直接找单点登陆的系统,会把登陆的信息存储到redis当中,然后返回一个token给客户端,当客户端在访问其他模块的时候把token放到请求头或者请求参数里面携带token,但是不能去操作redis,不然整个模块都要去连接redis,然后它会拿着token去请求单点登陆系统,可以使用HttpClient发送请求或者是dubbo远程调用

72. 自定义starter原理

spring boot提供了强大的starter为我们简化代码编写,有时候我们可以根据需要编写自己的starter,首先会导入基本的pom依赖,因为我们知道,当spring boot启动的时候会加载META-INF下的spring.factories配置文件中相应的配置类,所以我们会在resources目录里面创建META-INF/spring.factories文件,然后我们再创建自动装配的类,并且为了能够在yml文件灵活的修改自动装配配置,我们还会创建一个配置类,标上注解ConfigurationProperties以及定义好前缀和相应的属性,然后再自动装配类上标上注解Configuration相当于XML,以及注解ConditionalOnClass的生效类和注解EnableConfigurationProperties来激活配置类,然后再添加相应的自动装配的配置。配置完成之后加入到spring.factories文件里,最后安装到本地仓库。

73. 长时间登陆

如果说我们直接是放到redis当中,设置过期时间为3个月,那么其实是有问题的;redis的存储空间是非常宝贵的,成本是非常高的

一般会定义一个刷新token,当进行登陆的时候和之前一样,会生成一个token,失效时间为2小时,以及还会给app返回一个刷新token,并且将刷新token,和刷新token过期时间保存到数据库当中

当用户访问订单的服务,会带一个token,然后订单服务怎么知道它到底有没有登陆,因此会带着token访问登陆的服务,但是如果token过期了,那么就必须用刷新token获取一个新的token来返回给客户端

73. 单端重复登陆

首先会识别终端
用户最开始登陆一次,用户再次登录的时候删除上次登录的token,但是怎么知道上次登陆得token因为是随机生成得
首先会在数据库当中建立一个字段保存token,然后再登陆得时候判断数据库当中token是否为空

73. 多端重复登陆

首先要识别终端,到底是app登陆得还是web端登陆得,因此要接收请求头当中得User-Agent来接收,其次也是再数据库当中建立字段相应得字段,

74.HashMap底层原理

hash就是任意长度的输入转为固定长度的输出,在程序中碰到两个value值经过hash算法之后算法相同的值,也就是hash冲突,是不能够避免的,类上于抽屉原理,怎么设计优秀的hash算法:因为hash是高效率的算法,要做到长文本也能高效算出hash值,第二就是hash值不能够逆推出原文,第三就是两次输入有一点不同也要保证hash值不同,第四就是进来分散,避免hash冲突

当链表的size达到8并且table的长度达到64就会转为红黑树,否则他不会转化为树,他仅仅会发生一次resize,散列表扩容

当第一次添加的时候,首先会根据key算出hash值,经过hash值得扰动函数,使hash值更散列更均匀减少hash冲突,初始化hashmap对象,构造出Node对象,根据路由算法(tablie.length-1)&node.hash找出node应该存放在数组得位置,当hash发生冲突得时候导致链表长度很长,table长度达到64,链表长度达到8就会转为红黑树

扩容:为了解决哈希冲突导致的链化影响查询效率的问题,扩容会缓解该问题。

75. 如何保证token安全

我们需要保证token再中间网络传输得安全性,我们我们采用HTTP协议是不安全得,因为HTPP铭文传输,能够被窃取,HHTPS采用非对称加密公钥和私钥,公钥加密私钥解密,如果我们现在去部署网站我们要去申请证书,签发证书之后浏览器会校验证书得真伪,这样确保数据得安全

加密

  • 双向加密 既能够加密也能解密
    • 对称加密 加密和解密使用同一种密钥
    • 非对称加密 加密和解密使用不同密钥
  • 单向加密 只能加密,不能解密

证书:
HTTPS协议,那么服务器会给一个公钥,但是我怎么知道服务器给得公钥是靠谱得,因此网站需要申请SSL证书,浏览器需要向权威机构证明证书得合法性

76. HTTPS得加密过程

  • 服务器拥有一个公钥A,一个私钥B
  • 浏览器向服务器发起请求,服务器就把公钥A明文传输给浏览器
  • 浏览器拿到公钥A后,随机生成一密钥X,然后用公钥A进行加密传输给服务器
  • 服务器拿到后,用私钥B进行解密得到密钥X
  • 这时候浏览器和服务器手头上都有密钥X,之后就会用这个密钥X来进行对称性加解密

77. 为什么有了平衡树还要红黑树

虽然平衡树解决了二叉查找树退化为近似链表的缺点,能够把查找时间控制在O(logn),不过却不是最佳的,
因为平衡树要求每个节点的左子树和右子树的高度差至多等于1,这个要求实在是太严了,导致每次进行插入/删除节点的时候,几乎都会破坏平衡树的第二个规则,进而我们都需要通过左旋和右旋来进行调整,使之再次成为一颗符合要求的平衡树。

78. 红黑树性质

  • 每个节点要么是黑色要么是红色
  • 根节点是黑色
  • 每个叶子节点是黑色
  • 每个红色节点的两个子节点一定是黑色,不可能有两个红色节点相连
  • 任意一个节点到每个叶子节点路径包括相同的数量的黑节点
  • 如果一个节点存在黑子节点,那么该节点有两个子节点

左旋
以某个节点作为支点,其右子节点变为旋转节点的父节点,右子节点的左子节点变为旋转节点的右子节点

在这里插入图片描述
右旋
以某个节点作为支点,其左子节点变为旋转节点的父节点,左子节点的右节点变为旋转节点的左子节点
在这里插入图片描述

79. 什么是消息中间件

就是用来存储消息队列的软件,例如为了分析网站的用户行为,我们需要记录用户的访问日志。这些一条条的日志,可以看成是一条条的消息,我们可以将它们保存到消息队列中。将来有一些应用程序需要处理这些日志,就可以随时将这些消息取出来处理。

80. broker

一个Kafka的集群通常由多个broker组成,这样才能实现负载均衡、以及容错
broker是无状态(Sateless)的,它们是通过ZooKeeper来维护集群状态
ZK用来管理和协调broker,并且存储了Kafka的元数据(例如:有多少topic、partition、consumer)
ZK服务主要用于通知生产者和消费者Kafka集群中有新的broker加入、或者Kafka集群中出现故障的broker。

80. 分区

一个主题可以分成多个分区,如果没有分区,那么一个主题将会完整的保存到broker当中,但是我们为了保证高可用,如果broker挂掉,因此我们会复制一份做个副本

然后我们将一个topic分成三份来存储,分别存到三个broker上,如果其中的一个broker挂掉了不会影响到其他的broker

kafka集群可以有多个broker构成,一个主题有分区的概念,如果一个主题数据非常多,我们可以构建不同的分区分散到不同的broker当中去存储,解决的存储容量的问题,我们还可以根据分区建立副本解决了高可用的问题

当生产者发送消息到分区当中默认是轮询的机制

80. 主题

主题是一个逻辑概念,用于生产者发布数据,消费者拉取数据
Kafka中的主题必须要有标识符,而且是唯一的,Kafka中可以有任意数量的主题,没有数量上的限制
在主题中的消息是有结构的,一般一个主题包含某一类消息
一旦生产者发送消息到主题中,这些消息就不能被更新(更改)

80. 偏移量

offest记录着下一条将要发送给消费者的序号
默认Kafka将offset存储在zookeeper当中

81. 消费者组

如果解决消费者高可用问题,那么消费者可以集群构成消费者组,消费者组可以订阅topic主题消息
一个消费者组的消费者被称为woker
对于同一个消息一个消费者组只需要一个woker来进行处理

如果要实现单播:一个消息只需要一个消费者消费,那么只建一个消费者组
如果要实现多播:把消费者建立在不同的消费者组当中

80. 消息队列的应用场景

1、异步处理
例如新的用户注册的时候,当用户注册成功的时候我们会给他发个提示信息注册成功,先把用户的信息保存到数据库里面,然后再把注册的信息写到消息队列当中,然后直接响应给用户,由消息队列发送短信,节省了时间
在这里插入图片描述
2、系统解耦
订单系统需要保存用户的订单信息,需要调用库存减少的接口,如果库存系统出现了问题会导致订单系统无法工作,因此可以直接发给消息队列,即便库存系统挂了也没关系,等恢复了读取消息队列来执行,并且可用性也提高了

3、流量削峰
再抢票的时候,如果很多的用户直接来操作,直接压垮数据库,如果使用了消息中级件,可以把抢单的消息发送给中间件,业务系统去消息中间件当中读取

4、日志处理
大型的电商网站,根据用户访问的行为来推送用户喜好的情况,当用户点击某个商品的时候会给kafka发给消息,把这个日志记录下来

81. 缓存预热

假设系统很多酒店,在项目启动的时候,有许多请求访问不同的酒店那么就都会从缓存当中找,没有,再到数据库,从而打垮数据库,因此在启动项目的时候把缓存做好,因此我们需要配置监听器,监听Spring容器的创建,实现ApplicationListener接口,再项目启动的时候将所有的酒店信息刷新到缓存当中

82. 生产者幂等性问题

生产者往broker发送消息,broker把消息保存上了,实际上broker发送ACK给生产者网络丢失了,生产者以为没有发送成功,结果又重试机制重复发送,又把消息存储了一次

同一个消息只能保存一份,保存多份出现了幂等性问题
此时Kafka针对每个生产都会生成生产者id用来记录是哪个生产者发送了消息
以及Sequence Number针对每个生产者发送指定主题分区的消息都对应一个从0开始递增的标识
当我们在保存消息的时候,除了保存消息本身,还会保存这个消息发送者的pid和这个序号。

82. 消费者重复消费问题

当消费者消费成功之后会告诉MQ消息已经处理成功了并发送ACK以及偏移量,但是有可能就是再消费者消费成功之后ACK失败了,然后MQ就以为消息没有发送成功就触发重试机制又给消费者发送消息,那么就会产生重复消费的问题
首先我们需要再往MQ发送消息之前随机生成一个标记,然后拼接要发送的消息,监听器监听到之后获取消息,并解析出标记,从redis当中查找,如果不为null,则表示该消息已经消费过了直接返回,否则则进行消费,并且将标记存进redis

但是现在还有个问题,就是消息已经消费了,但是标记并没有存进到redis,那么就可能会造成重复消费的问题,因此我们可以将消息和标记控制再一个事务当中,加入到队列当中

如果是操作数据库就可以不同的方式,比如操作订单,订单提交之后订单的状态已经改了,我们只需要再查询数据库的数据进行判断就ok,处理了之后就不进行处理了

83. 多级缓存架构

在这里插入图片描述

  • 客户端:前端也可以进行缓存,浏览器本地也可以缓存
  • 应用层:静态资源的nginx也可以做缓存
  • 服务器:每个项目里面进程内的缓存、已经还有分布式缓存共享redis

84. CDN

内容分发网络,互联网静态资源的分发的主要手段,静态资源有一种特性不会经常变化,因此我们需要将他们缓存起来
CDN技术的核心是只能DNS,智能DNS会根据用户的IP自动确定就近的CDN节点

但是对于大多数的公司来说,用户并发量较少,其实并不需要额外部署CDN这种重量级的方法,利用Nginx静态资源缓存和压缩功能便可以胜任大多数企业应用场景

85. 进程内缓存

进程内缓存,项目运行起来缓存是保存再内存当中,每个项目都会保存进程内缓存,优点:redis还要去请求一次,而它不用去请求,直接再程序的内部去集成,缺点:每个项目再部署的时候都会保存缓存,造成数据冗余,缓存不一致的问题

86. 多级缓存架构

我们公司采用的是redis分布式缓存架构,当时我们也对预言,因为我们考虑到未来公司发展庞大,其实每个公司都有个远大的目标,如果是redis崩掉之后就会导致我们整个项目挂掉,因此我们在设计缓存之后我们要由进到远,由快到慢的结构,比如贵公司发展特别大,可以部署CDN的结点,用智能DNS用户在访问它的时候分配最近的CDN节点反馈静态资源,过后nginx也可以做静态资源的缓存,我们在服务器内不仅要进程内缓存以及要集中式缓存做配合,进程内缓存作为一级缓存,redis作为二级缓存,如果进程内有缓存从进程内,如果没有再从redis集中式缓存中找并写入到进程内缓存,如果都没有那么就从数据库当中去查询然后写入到redis和进程内缓存,因为我们项目是集群部署的,那我们怎么写入到这么多台进程服务上去?我们可以使用消息中间件,当我们在更新商品的时候,我们可以用kafka的多播,一个消费者组只能一个消费者收到消息,那我们可以建立多个消费者组然后更新到进程内缓存,并且更新到redis集中式缓存

87. AOP实现缓存

首先定义一个注解,因为这个注解被标记了才会做缓存,然后使用AOP的环绕通知,拦截到所有的service的所有方法,判断service有没有这个注解,如果有这个注解我们就要做缓存,如果没有的话我们就放行

88. 读写分离

解决读写分离有两种方案

  • 应用层:如果由select走读库,insert或uodate或delete走写库
    • 多数据源切换方便,程序自动完成、不需要引入中间件
    • 由程序员完成,运维不能做
    • 不能做到动态的添加数据源
  • 中间件:程序连接中间件,中间件完成读写分离
    • 源代码不需要任何改动就会实现读写分离
    • 程序依赖于中间件,会导致切换数据库变得困难;

89. AOP实现读写分离

在service方法中我们有以select开头有以写开头的,那我们配置的时候要配置写库和读库,那么在AOP开头当中判断如果是读开头的,然后根据对应的读写设置标记,根据标记来那么就走写库或者是读库。但是写库和读库的的数据是要同步的

90. 读写分离原理

在实际的项目当中我们可能会面临读多写少的场景,读的压力特别大,我们就可以采取读写分离,那我们读的话就要走读库,写的话就走写库,并且读库的数据和写库的数据要同步,同步的话我们就要有MySQL的主从复制主库开启二进制,从库读取二进制达到同步的作用,它的原理我们用到了spring动态的数据源,那我们就会配置主库的数据源和从库的数据源,然后我们就会编写动态数据源的类,他会从当前线程取到读库的标记还是写库的标记,那我们怎么才能将标记设置到当前线程呢?我们可以使用AOP的前置通知,在执行目标方法之前我们可以获取方法的前缀,如果是查询的方法我们就往当前线程中设置读库的标记,如果是写的话我们就标记写库的标记,那么动态数据源就能够获取到这个标记了,那么根据标记去执行具体的数据源,如果现在是一主多从,我们可以设置负载均衡策略轮询记录访问次数与台数取模或随机

91. ShardingJDBC配置

首先会导入pom依赖,然后再yml当中配置主库的数据源和从库的数据源,如果有多个从库就配置从库,并且配置负载均衡策略

92. 分库分表

在读写分离当中,主要解决了查询请求比较多高并发查询请求的压力

但是在互联网当中,数据量是非常大的,数据是存不下的,因此可以采用分库分表的思想

既分库又分表其实只需要在配置文件修改一个分片规则即可,不用修改业务任何代码。分库分表的数据表不能用自增主键。

然后采用ShardingJDBC实现分库分表,首先会导入对应的pom依赖,然后再yml当中编写分库的策略以及分表的策略

92. redis事务

redis的事务就是首先执行multi开启一个事务,把命令放到队列当中,然后执行exec就执行命令

93. redis如何保证高并发和高可用

高并发:
redis的单机并发能力达到几万不是问题,如果想要提高redis的并发能力,可以用redis的主从架构,redis天生支持一主多从的准备模式,单主负责写请求,多从负责读请求,主从之间异步复制,把主的数据同步到从。

高可用:
利用redis的主从架构解决redis的单点故障导致的不可用,然后如果使用的是主从架构,那么只需要增加哨兵机制即可,就可以实现,redis主实例宕机,自动会进行主备切换

94. redis主从复制

redis的复制功能是支持多个数据库之间的数据同步。一类是主数据库(master)一类是从数据库(slave),主数据库可以进行读写操作,当发生写操作的时候自动将数据同步到从数据库,而从数据库一般是只读的

  • 当一个从数据库启动的时候,会向主数据库发送sync命令
  • 主数据库接收到sync命令后会开始在后台保存快照(执行rdb操作),并将保存期间接收到的命令缓存起来
  • 当快照完成之后,redis会将快照文件和所有的缓存命令发送给从数据库
  • 当从数据库收到之后,会保存到磁盘当中然后载入快照文件并执行收到的缓存文件
    在这里插入图片描述

95. redis哨兵模式

是一种特殊的模式,哨兵是一个独立的进程,作为进程,他会独立的运行,哨兵会发生ping的命令,等待redis服务器的响应,如果再规定的时间内,主机redis无响应,从机则判断主机宕机,选举从机上位,然后通过发布订阅模式通知其他从服务器,修改配置文件
哨兵至少要有三个才可以,如果只有两个哨兵,因为每个redis节点上都会安装一个哨兵监控,其中的master宕机了,那么哨兵也会宕机,那么就只有一个哨兵了,但是2的majority是2,所以至少有两个哨兵认为宕机才会执行故障转移

哨兵之间相互监控并且也监控redis的节点

哨兵判断细节
主观下线:
使用于所有的主节点和从节点,适用于所有主节点和从节点,哨兵没有收到目标节点的回复,则会判断该节点主观下线,只有半数哨兵节点都主观判断则主节点下线

客观下线:
哨兵会发送命令向其他哨兵询问该节点的状态

96. redis集群脑裂

因为网络问题导致redis主节点和从节点以及哨兵处于不同的网络分区,此时哨兵无法感知主节点的存在,所以将从节点提升为主节点,此时存在两个主节点,就像一个大脑分裂成了两个。

在这个问题当中,如果客户端还在基于原来的master节点继续写入数据,那么新的master节点将无法同步这些数据,当网络问题解决之后,sentinel集群将原先的master节点降为slave节点,此时再从新的master中同步数据,将会造成大量的数据丢失。

设置参数解决

  • 连接到master的最少slave数量
  • slave连接到master的最大延迟时间

97. redis的数据存储

redis的主从架构主要解决高可用和高并发问题
从节点和主节点的数据是一模一样的,但是如果数据存储满了怎么办?

首先会有多个master

如果用客户端分片的方式解决
例如存储abc,他又ascii码值,然后我们用它的总共ascii的码值对服务器的台数取模,取模的结果存储到相应的服务器上
在这里插入图片描述

redis解决方案

redis的每一个主节点都可以和任意的主节点进行相互的通信并形成了环状,原来的解决方案是到底往哪个节点上存,往哪个节点上取,当添加的时候用CRC16算法针对key然后取模上卡槽的个数并存储到对应的节点上,redis cluster采用虚拟槽分区,分为0-16383个整数槽,每个redis主节点平分维护一部分的槽

97. redis过期时间

redis之前设置了过期时间,那如果时间到了之后数据一定删除了吗?
不一定
如果现在redis某个key过期了,那么redis是怎么知道呢?
定时删除+定期删除+惰性删除

定期删除:redis每隔100ms会随机的抽取一些设置过期时间的key,看是否过期,如果过期了那么就会把他删掉,那么这个时候可能会有很多没有检测到,所以有惰性删除

惰性删除:就是当我们查询key的时候看有没有过期,如果过期了就会删除

那如果定期删除也没有删除掉,用户也没有去查询,那么大量的过期key堆积再内存当中,导致redis的内存耗尽了,那么就会用到redis的淘汰机制

内存淘汰机制:

  • LRU:最近最少使用到的
  • LFU:最不常用的
  • ttl:从已设置过期时间的数据集中挑选将要过期的数据淘汰
  • random:从已设置过期时间的数据集中挑选任意数据淘汰

97. redis是单线程的,为什么并发这么高

redis的单线程是指请求的时候用单线程IO多路复用程序,好处就是避免多线程的竞争问题,如果是incr整数递增,如果是多线程的话可能会造成线程安全问题,如果是单线程不管又多少socket来连接都有先后的顺序,就没有线程安全的问题,使用单线程也避免的多线程的切换,消耗性能

redis客户端执行某个命令的时候要与redis建立socket连接,最前面是IO多路复用是个单线程的,我们所有的请求都是到IO多路复用程序,然后请求会进行排队,然后有文件时间分派器来处理请求

redis采用IO多路复用这样的模型,请求都是请求到IO多路复用程序单线程的请求,并且会把请求串行化,避免多线程的竞争也避免CPU的切换消耗性能,但是再处理的时候是由文件事件分配器具体的处理器去执行

97. redis持久化机制

基于RDB二进制的备份机制,基于AOF日志的备份机制
默认AOF是关闭的

RDB是一定的事件间隔进行备份,虽然性能很高但是会存在丢失数据的风险
AOF日志的方式是把命令计入到文件当中,丢失的风险低,性能非常低

如果备份的磁盘顺坏了怎么办?
我们可以写个程序把日志备份的数据写入到另外的另外的机器上去做成高可用的架构

98. Zookeeper选举的原理

在Zookeeper集群中,每个节点都会投票,如果某个节点获得超过半数以上的节点的投票,那么这个节点就是leader节点。只有一台服务器启动的时候他会一直处于寻找状态,第二台出现的时候会相互投票,直到第三台出现才会产生一个leader,所以最少都要三台服务器。一旦产生了leader,后面即使票数更大也不能作为leader。直到leader节点挂掉,才会选举另一个最高票数的服务器作为leader

启动方式的选举
如果节点越新,就表示个人能力越强,zxid(事务)就越大
在集群选举最开始的时候,节点认为自己是最强的,会给自己投一票,紧接着会将选票投给其他节点,同时自己也会收到其他节点传过来的选票,每个节点收到选票会判断其他节点是否比自己强,如果是那么就改票,一旦集群超过半数的人都认为某个节点强,则就是领导者

服务器一启动的时候会投自己一票,第二个启动的时候也会投自己一票,根据事务的id,一节点比二节点的事务id低,会该票投给二票,但是还是不满足zookeeper集群至少3台,第三台启动的时候会重新进行投票,节点一和二发现事务的id小于三的事务id,然后又会改票,把票投给三,然后选举为leader
Leader挂掉之后的选举
当非Leader加入或者宕机不会影响Leader,但是一旦Leader挂了。那么整个zookeeper集群将会暂停对外服务,触发新一轮的选举
在这里插入图片描述
Server Id:服务器Id
Zxid:事务id
Epoch:逻辑时钟
Server状态:选举状态

  • LOOKING 竞选状态
  • FOLLOWING:随从状态,参与投票
  • OBSERVING:观察状态,不参与投票
  • LEADER:领导者状态

总结:

  • Zookeeper选举会发生再初始状态和运行状态
  • 初始状态下会根据服务器sid的编号对比,编号越大权值越大,投票过半数即可选出Leader。
  • Leader宕机会触发新一轮的,选举zxid越大,权值越大

99. Zookeeper为什么是奇数且必须至少三个以上

所以在ZooKeeper的选举过程中,为了能够成功选举出leader,那就一定不能出现两台机器得票数一致的情况,所以在部署ZooKeeper集群的时候,节点数一定要为奇数,也就是2n+1台。

存活的机器必须大于N+1台

100. Zookeeper如何保证主从的数据一致性的?

Zookeeper是一主多从的架构,如何保证主从架构的数据同步?
Zookeeper会通过消息的广播,把更新的消息通知给所有的从节点,Leader挂了服务器所有的节点会进入LOOKING状态,然后选举出新的Leader服务器

101. 为什么Dubbo选择Zookeeper作为注册中心?

  • 注册中心的承载能力有限,而Zookeeper集群配合web应用很容易达到负载均衡。
  • zk支持监听事件,特别适合发布/订阅的场景,提供程序意外停止时,注册表服务器可以自动删除其信息

102. 什么是微服务

随着项目的不断发展,规模不断扩大,开发人员的不断增多,如果再同一个项目下开发,代码冲突非常严重,也不方便项目技术的水平扩展,不利于高并发的处理,如果我们对他部署集群,那么就会对整个项目集群,那么就要把项目拆分,冲突减少,而且各个项目可以自己的技术选型

103. zookeeper和eureka的区别

服务的消费者和zookeeper保持长连接,一直维持这个连接,如果断开之后,zookeeper会删出连接,是watch机制,监听到节点变化及时去更新

在服务注册后,Eureka Client会维护一个心跳来持续通知Eureka Server,说明服务一直处于可用状态,防止被剔除。默认每隔30秒,再向eureka进行注册的时候主动向他发消息,获取注册中心的服务清单,并且缓存在客户端本地。

104. Eureka集群的工作原理

比说说现在有三台 Eureka Server 组成的集群,一台再成都,一台再北京,一台再上海,Eureka的集群通过复制来同步数据,相互之间不区分主节点和从节点,所有的节点都是平等的,相互注册来提高可用性,如果某台 Eureka Server 宕机,Client 的请求会自动切换到新的 Eureka Server 节点。当宕机的服务器重新恢复后,Eureka 会再次将其纳入到服务器集群管理之中。Eureka Server 集群之间的状态是采用异步方式同步的,所以不保证节点间的状态一定是一致的,不过基本能保证最终状态是一致的。

105. CAP理论

分布式系统只能再一致性,可用性,分区容错性满足两种不能兼容三种

  • 一致性:服务A、B、C三个结点都存储了用户数据, 三个结点的数据需要保持同一时刻数据一致性。
  • 可用性:服务A、B、C三个结点,其中一个结点宕机不影响整个集群对外提供服务,如果只有服务A宕机点,当服务A宕机整个系统将无法提供服务,增加服务B、C是为了保证系统的可用性。
  • 分区容忍性:分区容忍性就是允许系统通过网络协同工作,分区容忍性要解决由于网络分区导致数据的不完整及无法访问等问题

可用性越高,那么一致性就会越差,因为可用性高了就会增加服务器,那么服务器的同步延迟就会更大,一致性减少

CAP有哪些组合方式?

  • CA:放弃分区容忍性
  • AP:放弃一致性,加强可用性和分区容错性,类似于微信转账,两个小时到账
  • CP:放弃可用性,加强一致性和分区容忍性,例如跨行转账,一次转账请求要等待双方银行系统都完成整个事务才算完成。

106. Eureka注册延迟

Eureka Client 启动之后,不是立即向 Eureka Server 注册的,而是有一个延迟向服务端注册 的时间。通过跟踪源码,可以发现默认的延迟时间为 40 秒

107. Eureka响应缓存

Eureka Server 维护每 30 秒更新一次响应缓存,所以即使是刚刚注册的实例,也不会立即出现在服务注册列表中

108. Eureka服务注册

Eureka Client会通过发送REST请求的方式,向Eureka Server注册自己的服务。注册时,提供自身的元数据,比如ip地址、端口、运行状况指标、主页地址等信息

109. 服务续约

在服务注册后,Eureka Client会维护一个心跳来持续通知Eureka Server,说明服务一直处于可用状态,防止被剔除,默认每隔30秒发送一次心跳来进行服务续约。

110. Ribbon负载均衡策略原理

当订单微服务通过RestTemplate调用商品微服务的时候被LoadBlancerInterceptor所拦截掉,获取到服务集群的所有地址,随后利用负载均衡算法得到真正服务地址替换掉服务的名称信息进行访问

111. Feign远程调用原理

通过RestTemplate我们知道,当订单调用商品的时候还要写商品的接口,以及实现类,是一个Http请求调用的轻量级框架,是以Http接口的方式调用Http接口,

与dubbo相比,dubbo服务的提供方需要编写接口的实现类,底层是通过NIO的socket协议,首先对应Feign来说需要定义Feign的接口,配置服务的名称以及调用的地址,然后会在服务的调用方注入Feign的接口,这个接口底层其实会去创建代理对象,当代理对象的任何方法被调用的时候都会触发invoke方法,因为再Feign的接口上配置了服务的名称,会根据服务的名称去Eureka里面获取服务的列表,然后再根据相应的负载均衡策略去决定哪一台服务器,然后再发起http请求,去请求提供发,并且提供方也会创建一个代理对象,然后返回json数据给调用方并且反序列化为pojo

当订单服务调用商品服务调用一次,并且调用成功,但是由于网络的问题,出现了网络的延迟,又发起了重试机制导致商品服务被调用了两次或三次,产生了幂等性问题,解决:我们可以再商品服务那边首先我们需要随机生成一个标记,然后从redis当中查找,如果不为null,则表示该消息已经消费过了直接返回

112. Zuul网关作用

  • 统一入口:为服务提供一个唯一的入口
  • 鉴权校验:识别每个请求的权限。拒绝不符合要求的请求
    • 如果是购物车微服务我们要写拦截器判断是否登陆,订单微服务我们也要判断是否登陆,重复代码,和nginx不一样,因为他不是由Java代码编写的,不能再nginx当中写Java代码
  • 负载均衡

113. 网关挂了怎么办

  • 可以用keepalived服务监控软件,做一个虚拟的ip然后做一个主备切换
  • 可以用nginx对网关做集群

114. 什么是Hystrix

在分布式的,许多的服务依赖项中的部分服务必然有概率出现失败。Hystrix是一个库,通过添加延迟和容错逻辑,来帮助你控制这些分布式服务之间的交互

115. 服务雪崩是如何发生的

  • 在微服务当中,一个请求可能需要多个微服务的接口实现,会形成复杂的调用链路
  • 如果某个服务出现异常,请求阻塞,用户得不到响应,容器中线程不会释放,于是越来越多用户请求堆积,越来越多线程阻塞。
  • 下游服务如果调不通,会导致请求产生积压,大量请求积压在当前服务器导致当前服务也宕机了。也会导致上上游的服务也挂掉了

如果下游的服务不可以,那么Hystrix可以快速的进行熔断,熔断了之后就会去做一个降级的处理,直接调用本地的方案来做快速的返回

如果下游的服务正常了,那么Hystrix就会断开熔断,然后恢复

116. Hystrix熔断器状态

  • 关闭状态,所有请求正常访问
  • 打开状态,所有请求都会被降级。
    • Hystrix会对请求情况计数,当一定时间失败请求百分比达到阈值,则触发熔断,断路器完全打开
    • 默认失败比例的阈值是50%,请求次数最低不少于20次
  • 半开状态
    • 打开状态不是永久的,打开一会后会进入休眠时间(默认5秒)。休眠时间过后会进入半开状态。
    • 半开状态:熔断器会判断下一次请求的返回状况,如果成功,熔断器切回关闭状态。如果失败,熔断器切回打开状态。

117. 信号量隔离

上游服务不要被上游服务拖垮,下游服务不要被上游服务压垮

比如现在有支付服务和用户同时去访问订单,我们应该把订单保护起来,避免压垮

在Hystrix当中信号量隔离,有个计数器,每秒允许100个请求,如果1秒的请求超过了100次,直接熔断,意思是只能处理100个请求,超过100个就不会处理
使用一个原子计数器(或信号量)来记录当前有多少个线程在运行,当请求进来时先判断计数 器的数值,若超过设置的最大线程个数则拒绝该请求,若不超过则通行,这时候计数器+1,请求返 回成功后计数器-1

118. 线程池隔离

一个线程的压力过大会影响到其他的接口,那么可以针对不同的接口创建线程池,如果当A的访问量增大之后,其他接口不会受到影响,互不干扰

优点:

  • 当线程池出现问题时,线程池隔离是独立的不会影响其他服务和接口
  • 当失败的服务再次变得可用时,线程池将清理并可立即恢复,而不需要一个长时间的恢复

缺点

  • 线程池隔离的主要缺点是它们增加计算开销(CPU)

119. 线程池隔离和信号量隔离的区别

线程池隔离:
1、 第三方应用或者接口
2、 并发量大

信号量隔离:
1、 内部应用或者中间件(redis)
2、 并发需求不大
在这里插入图片描述

120. 怎么保护服务

如果调用下游服务的时候如果达到了50%默认都没有调通,这个时候熔断器就会打开,打开之间就会调用本地的方案进行降级处理,如果现在5s或者5s之后是半开状态,之后如果下游服务能够调通那么熔断器就会关闭,否则继续熔断,然后针对本身也会进行保护,也可能面临上游的压力,这个时候我们可以用线程池隔离和信号量隔离,我们可以针对每个接口单独创建一个线程池,如果某个接口并发量特别大不会影响到其他的接口并且能够提高并发的访问量,第二我们可以用信号量隔离他会维护一个原子类计数器,如果当我们每秒的qps超出了我们配置的范围,那么就会被拒绝掉那么可以达到保护服务本身的作用

121. config配置中心

在微服务的下面我们有很多的项目,我们的配置很多,我们是应该配置统一的管理起来

122. nginx挂了怎么办

如果直接配两台nginx,一台挂了还有另外一台
但是前端访问的入库是访问nginx,我们只会返回给他一个ip,现在两台nginx都起动。如果其中一台挂了,我们不可能让前端再去改代码改ip
因此客户端再访问服务器的时候实际上是访问keepalived的虚拟ip,虚拟ip指向的是主nginx的服务器,并且会进行心跳检查,一旦检查到主nginx挂了,就会指向备nginx,如果主机恢复,那么keepalived就会切回主机
keepalived就可以防治单点故障,实现主备机制

123. nginx只有5万并发,那更大呢?扛不住

可以使用LVS。Linux虚拟服务器
LVS是在操作系统上面进行的,效率比nginx高一些
可以使用LVS加上nginx实现负载均衡
一级负载使用LVS,二级负载使用nginx

如果LVS也挂了可以向nginx一样用keepalived的虚拟ip实现主备切换,但是如果lvs扛不住并发只能使用F5在前面在挂一个F5硬件负载均衡
如果这些还不满足,可以采用DNS解析的方案。

硬件负载均衡主要有:F5、Array、NetScaler等
软件负载均衡主要有:LVS、HAProxy、Nginx等

124. 如果Mysql当中的master的宕机了怎么办

对于主从复制来说,其内部会存在一台master以及一台或多台slave。但有一个非常明显的问题,master是单点存在。一旦master宕机,则无法进行数据的写入
主主架构
会存在两台master,没有slave。并且会对这两台master进行读写分离,两台master会进行相互的复制。因为假设现在负责写的master宕机了,那么写的工作则会交给之前负责读的服务器来完成,相当于它即负责写又负责读。等到原先负责写的master恢复了,其在继续负责写工作

此时缺点也非常明显,虽然master不存在单点了,但是对于读来说,如果并发量大的话,它肯定扛不住。对于主主复制架构来说,应用较少

级联架构
当master向slave进行复制的时候,对于slave可以分为多层, master只要向其中两台slave复制即可,然后再由slave将其数据复制到后面更多的slave中。

但是这种架构也存在一个弊端:slave的延迟会加大。

MySQL主主高可用架构
现在不管写或者读,只要其中一个宕机,则会把它本身工作交给另外一台服务器完成。此时就需要对IP进行一个自动的指向。而且这种服务器IP切换,就可以使用keepalived来完成ip的切换

对于keepalived会在多台mysql服务器进行安装, 同时keepalived间也分为master和slave, 同时master会虚拟化一个VIP供应用进行连接。 如果一旦master挂掉后,会由slave节点继续工作,同时slave节点也会虚拟出相同VIP,供应用进行连接

125. MySQL挂了怎么办

MySQL可以使用读写分离一个主库多个从库来缓解读的压力,因为主节点只有一个,可能会存在但主节点故障的问题,我们可以采用双主结果,双主结果两边采用复制,但是现在有两个ip,我们可以采用keepalived用虚拟ip来实现ip之间的切换

126. 消息丢失

生产者往消息中间件发送消息,消息丢了怎么办?
我们需要再发消息的时候配置将消息持久化,如果没有持久化,消息是在内存当中的,如果持久化了消息会同步的保存到磁盘当中

127. activemq主题模式

在发送方
在这里插入图片描述
消费者
在这里插入图片描述

128. 进程和线程的区别

进程是操作系统的最小单元,线程是操作系统调度的最小单元
一个程序至少有一个进程,一个进程至少有一个线程

129. 死锁

是指两个进程或者线程在执行的过程当中,因争夺资源而造成的一种互相等待的现象

130. SynchronizedMap 和ConcurrentHashMap 有什么区别?

SynchronizedMap是一次性锁住整张表来保证线程安全问题,每次只能有一个线程来访问map

ConcurrentHashMap 使用分段锁来保证在多线程下的性能,则是一次性锁住一个桶,ConcurrentHashMap 默认将hash 表分为16 个桶,诸如get,put,remove 等常用操作只锁当前需要用到的桶。这样,原来只能一个线程进入,现在却能同时有16 个写线程执行,并发性能的提升是显而易见的。

131. 幂等性

HTTP 方法的幂等性是指一次和多次请求某一个资源应该具有同样的副作用。说白了就是,同一个请求,发送一次和发送 N 次效果是一样的!幂等性是分布式系统设计中十分重要的概念,而 HTTP 的分布式本质也决定了它在 HTTP 中具有重要地位。

withdraw 的语义是从 account_id 对应的账户中扣除 amount 数额的钱;如果扣除成功则返回 true,账户余额减少 amount;如果扣除失败则返回 false,账户余额不变。

一种典型的情况是 withdraw 请求已经被服务器端正确处理,但服务器端的返回结果由于网络等原因被掉丢了,导致客户端无法得知处理结果。如果是在网页上,一些不恰当的设计可能会使用户认为上一次操作失败了,然后刷新页面,这就导致了 withdraw 被调用两次,账户也被多扣了一次钱。
在这里插入图片描述

132. nacos的发展历史

在2007年,nacos起源于淘宝的五彩石成就了configserver的前身
2010年zookeeper成立
2011年阿里开源了dubbo,并将zookeeper作为注册中心
2018年因为springcloud没有维护了,所以把configserver开源成为了nacos

133. nacos注册中心

  • 配置文件相对分散。在一个微服务架构下,配置文件会随着微服务的增多变的越来越多,而且分散在各个微服务中,不好统一配置和管理
  • 配置文件无法区分环境,可能是生产环境,可能是开发环境
  • 配置文件无法实时更新

134. nacos注冊中心原理

zookeeper的消费者和zookeeper需要建立监听的机制,监听机制是TCP,是长连接,一直保存连接,如果服务的消费者有3000台机器,那么3000台机器都要与zookeeper保存长连接,性能低

服务器的推送是走的强制一致性的过程,走的是UDP协议,但是UDP可能会造成推送失败,然后客户端又用最终一致性将客户端每隔10s主动拉取得方案取进行互补

nacos的提供方会每5s向服务器发送请求证明自己还存活,这样做可以不用建立长连接提高了性能

服务的提供方启动的时候会发送openApi发起服务器的注册,与nacos建立心跳,有服务的提高方每5s向服务器发送心跳证明活着,如果说现在服务的提供方有节点的新增或者挂掉了节点产生变化,在服务器的消费者首先会获取服务器的列表,如果说列表产生变化,那么nacos会用UDP主动推送消息,因为UDP的性能比较高,但是不可靠可能会存在部分通知失败的情况,所以nacos客户端又采用主动每隔10s去拉去一次数据,这是一个最终一致性。相当于服务器的推送和拉去互补,强一致性和最终一致性的结合

nacos把zookeeper和eureka的优点结合起来

注册中心在启动的时候,实际上会初始化一个线程池以及会创建notifier的任务,这个任务会无限循环,监听阻塞队列当中的任务,当服务提供者来调用nacos的openApi来进行注册的时候,nacos会将请求放到阻塞队列当中,然后由在后台运行的线程池来读取阻塞队列当中的任务,将请求和处理的操作分离开,提高了整体的性能

135. 动态刷新原理

两种方式

第一:服务器主动将数据变更推给客户端
如果要推,服务器端需要保持大量的连接,并且检测连接有效性需要心跳机制维护,要保持长连接,效率低

第二:客户端主动向服务器拉取数据
客户端去服务器端拉取数据,需要在效率和时效性寻找一个平衡

首先客户端每10ms主动发起请求,如果有变化就返回变化的结果,没变化就返回没变化的结果,这样是不行的,因为客户端每10ms发送,这10ms过的非常的快,客户端会频繁的请求配置中心,性能非常低,如果客户端请求的时间特别长,那么时效性就非常低,如果服务器推的话要建立TCP请求,要建立心跳,耗费大量的性能

首先客户端每10ms主动发起请求,nacos配置中心会把请求放入到队列当中,会把客户端的连接请求阻塞住,阻塞时间为30s,30s生产了两种情况,如果30s内配置中心有变化那么通过刚才的请求会把变化的结果直接返回,如果30s都没有产生变化,直到30s才返回一个没有变化的结果

客户端主动请求nacos不用跟他建立心跳检测了,因为如果下一次客户端没有请求那么就证明客户端已经挂了

当我们修改配置中心的配置之后,如果服务器主动服务器主动将数据变更推给客户端,那么会保持长连接,维护心跳,浪费服务器性能,如果客户端主动向服务器拉取数据,如果间隔比较短的话性能非常,客户短会大量请求服务器,如果间隔比较长的话,那么时效性就会比较低

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
JavaJDBC事务是一种机制,用于控制数据库操作的一组相关操作,可以确保数据库的完整性和一致性。 JDBC事务需要通过以下步骤进行管理: 1. 打开数据库连接:在执行任何数据库操作之前,必须首先打开与数据库的连接。可以使用JDBC提供的DriverManager类的getConnection()方法来建立连接。 2. 开始事务:一旦连接成功建立,就可以开始事务。通过将连接的setAutoCommit()方法设置为false来禁用自动提交。 3. 执行数据库操作:在事务中,可以执行多个数据库操作(如插入、更新、删除等)。所有这些操作都将被视为一个原子操作,要么全部成功执行,要么全部回滚。 4. 提交事务:当所有数据库操作都成功执行时,可以通过调用连接的commit()方法来提交事务。这将导致所有在事务中进行的操作永久性地保存到数据库中。 5. 回滚事务:如果在事务中的任何操作失败或出现异常,可以通过调用连接的rollback()方法来回滚事务。这将导致在事务中发生的所有操作被撤消,数据库回到事务开始之前的状态。 6. 关闭连接:无论事务是提交还是回滚,都需要最终关闭与数据库的连接。这可以通过调用连接的close()方法来完成。 JDBC事务的好处包括:确保数据一致性,防止数据丢失或不一致;提高数据操作性能,通过将多个操作作为一个原子操作来减少网络开销;增加数据安全性,可以使用事务来保护敏感数据。 总的来说,JDBC事务是一种用于管理数据库操作的机制,可以确保数据库的完整性和一致性,并提供了多个操作的原子性,从而提高了数据的安全性和性能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值