Java 面试必会(应届必备)

SSM 

1. GET和POST请求的区别?

① GET(获取&检索)从服务器上获取数据。

① POST(创建&更新)向服务器传送数据。

误区:不是获取数据只能用GET,只是通常用GET获取数据,这可以认为是一种约定,并非规定,实际上只要你的后端支持,用POST也可以获取数据


② GET方式不安全,参数包含在URL中,在地址栏可见请求信息。

GET : http://localhost:8080/suke/login?uname=admin&pwd=root&address=USA

② POST方式相对安全,请求信息放置在请求体(request body)中,在地址栏不可见。

POST : http://localhost:8080/suke/login

注意:如果GET和POST没有加密,它们安全级别都是一样的,随便一个监听器都可以把所有的数据监听到。


③ GET方式发送内容的长度受限于地址栏有限长度,也就意味着数据量不能大于2KB。

③ POST方式一般被默认内容长度不受限制。


④ GET请求适用于不改变服务端数据请求的原则,所以可以被缓存。

④ POST请求会更改服务器端数据请求,所以不可以被缓存。

2. Mybatis中使用${}和使用#{}的区别

#{}是预编译处理:1.传入的参数在sql中显示为字符串。2.方式能够很大程度防止sql注入

${}是字符串处理:1.传入的参数在sql中直接显示为传入的值。2.方式无法防止Sql注入。

3. 说一下 MyBatis 的一级缓存和二级缓存?

一级缓存是默认使用的,一级缓存的作用域是一个sqlsession内;

二级缓存需要手动开启,二级缓存作用域是针对Mapper(Namespace)进行缓存;默认不打开二级缓存,要开启二级缓存,使用二级缓存属性类需要实现 Serializable 序列化接口(可用来保存对象的状态)。

开启二级缓存数据查询流程:二级缓存 -> 一级缓存 -> 数据库。

一级缓存:是基于数据库会话的,并且默认开启。

一级缓存的作用域为SqlSession。

在同一个SqlSession中,执行相同的sql语句,那么第一次就会去数据库中进行查询,并写到缓存中,如果我们后面还想去访问数据库查询,就直接去一级缓存中获取就可以了。

二级缓存:是基于全局的,不能默认开启,开启时需要手动配置。

二级缓存的作用域为SqlSessionFactory,是一个映射器级别的缓存,针对不同namespace的映射器。

一个会话中,查询一条数据,这个数据会被放到一级缓存中,但是一旦这个会话关闭,一级缓存中的数据就会被保存到二级缓存。新的会话查询信息就会参照二级缓存中存储的信息。

4. Servlet的生命周期

(1)Servlet生命周期经过4个阶段:实例化、初始化、服务、销毁
(2)实例化 其实就是调用 无参构造方法
(3)初始化 其实就是调用init方法
(4)服务   其实就是调用service方法
(5)销毁   其实就是调用destroy方法

(6)代码结论:
    - Servlet默认情况下tomcat在启动的时候,它不会实例化初始化
    - 当第一次给Hello01Servlet发请求时,它会实例化、初始化、服务。以后每次访问都是直接服务
    - 当tomcat服务器停止时,Hello01Servlet的销毁方法会被执行

5. Spring框架IOC和AOP的原理?

IOC(Inverse of Control:控制反转)是一种设计思想,就是将原本在程序中手动创建对象的控制权,交由Spring框架来管理。

在spring的眼中,一切皆javabean,都可以存放到容器中管理。
① 控制,指的是对javabean的生命周期的控制权:何时创建,何时销毁,是否单例,初始化时的操作等等
② 反转,指的是javabean上面的操作之前是由程序员控制的,现在我们交给容器去管理。

DI: 依赖注入。
① 依赖,指的是 对象和对象之间存在依赖关系
② 注入,指的是 对象和对象之间的依赖关系之前是程序员注入设置的(一般通过setter进行设置,或者主动获取(主动创建))

现在,我们有了IOC容器,那么除了对象的生命周期由容器维护之外,容器也负责对象和对象之间依赖关系的维护。


IOC思想是基于IOC容器来完成的,IOC容器底层就是对象工厂(BeanFactory接口)。

IOC原理是基于xml解析、工厂设计模式、反射来实现的。

IOC容器实际上就是个Map(key,value)Map 中存放的是各种对象。

IOC容器的核心对象:BeanFactory , 子接口ApplicationContext 两个常用的实现类:ClassPathXmlApplicationContext(从类路径加载) , FileSystemXmlApplicationContext(从文件路径加载)


通俗易懂的一句话结论:之前需要我们自己手动new对象的,但是我们现在不需要反复去new对象了,而是把new对象的主动权交给IOC容器,我们什么时候用什么时候取就可以了。

随着技术越来越先进,在Spring原生中我们觉得xml文件中配置还是有点麻烦,后来就有了SpringBoot内部集成了这些功能,我们直接一个注解就可以了,方便了很多。

AOP(Aspect-Oriented Programming:面向切面编程) AOP代表的是一个横向的关系,剖开对象的内部,并且把影响多个类的共同行为抽取出来,作为公共模块(叫做切面Aspect),然后再通过织入的方式把这个切面放进去。理解来说:就是能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。


通俗易懂的一句话结论:就是不通过修改源代码方式,在主干功能里面添加新功能。

AOP底层是通过动态代理来实现的,同时有JDK动态代理CGLIB动态代理两种方式:

        1.有接口的情况,使用 JDK 动态代理,即创建接口实现类代理对象,增强类的方法。

        2.没有接口的情况,使用 CGLIB 动态代理,即创建子类的代理对象,增强类的方法。


AOP的专业术语

   1.Advice (增强/通知) 表示需要扩展的功能,所在的类叫做增强类/通知类。

   2.JoinPoint(连接点)程序执行的某个特定位置

   3.PointCut(切入点)AOP 通过切点来定位特定的连接点

   4.Aspect(切面)切面由切点和增强组成,他既包含横切的定义,也包括了连接点的定义。 SpringAOP就是负责实施切面的框架,他将切面定义为横切逻辑织入到切面所指定的连接点。

   5.织入(weaving) 就是把Advice添加到目标类的连接点的过程

   6.目标对象 顾名思义:要增强到具体的对象


通知方法Advice:五种通知方法

  • @Before:前置通知,在目标方法执行之前执行
  • @AfterReturning:返回(后)通知,在目标方法返回之后执行,对返回值进行增强
  • @AfterThrowing:异常(后)通知,在目标方法出现异常之后执行,对异常信息进行增强
  • @After:后置通知、最终通知(finally),在目标方法执行完之后执行
  • @Around:环绕通知,在目标方法前后都可以增强

数据库事务管理其实就是典型的AOP应用

6. 什么是静态代理、动态代理:JDK/CGLIB?

代理模式:目标对象不可访问,通过代理对象增强访问。

案例:客户A想要租房,都是通过与中介去租房,而不是直接去找房东租房。这就是代理模式。

静态代理的特点:

  • 目标对象和代理对象实现同一个业务接口
  • 目标对象必须实现接口
  • 代理对象在程序运行前就已经存在
  • 能够灵活地进行目标对象的切换,却不能进行功能的灵活处理(就因为这点问题,而出现了动态代理)

动态代理:代理对象在程序运行的过程中动态在内存构建,可以灵活的进行业务逻辑功能的切换。简而言之,就是基于静态代理,突破其业务功能必须固定这一瓶颈。


JDK proxy VS CGLIB proxy区别?

语法上: 
① JDK动态代理需要目标类有父接口
② JDK动态代理产生的代理对象是目标类的兄弟
③ CGLIB动态代理不需要父接口,但是目标类不能被final修饰

④ CGLIB动态代理产生的代理对象是目标类的子类
性能上:
① JDK产生代理对象的性能较高;CGLIB较低

② JDK动态代理调用方法的性能较低;CGLIB较高
③ 两者的适用场合不同:

JDK动态代理适合于频繁创建的场景;

如果是单例或对象池的场景(无需频繁创建代理对象),但需要频繁调用代理方法,优先考虑CGLIB动态代理。

7. SpringMVC生命周期? 

1. 用户发送请求到前端控制器DispatcherServlet

2. 前端控制器请求处理映射器去查找处理器Handler

3. 找到处理器以后处理器映射器向前端控制器返回一个执行链HandlerExecuteChina

4. 前端控制器请求处理器适配器HandlerAdapter

5. 处理器适配器执行处理器(Handler)

6. 处理器执行完后给处理器适配器返回ModelAndView

7. 处理器适配器向前端控制器返回ModelAndView

8. 前端控制器请求视图解析器(ViewResolver)去进行视图解析

9. 视图解析器向前端控制器返回View

10. 前端控制器对视图进行渲染

11. 视图将渲染结果返回给前端控制器

12. 前端控制器向用户响应

8. Maven中A依赖B,B依赖C,那么A可以使用C中的类吗?

此时要看B依赖C时的范围,如果是compile范围则A可以使用C,如果是test或provided范围则A不能使用C。

9. 下面依赖信息对应的jar包在Maven仓库根目录下的路径是什么?

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.1</version>
</dependency>

Maven本地库根目录/org/apache/commons/commons-lang3/3.1/commons-lang3-3.1.jar

10. 通过Maven下载jar包,下载失败了怎么办?

(提示:分*.lastUpdated和内部损坏两种情况说明)

① *.lastUpdated情况:将*.lastUpdated文件删除,重新下载。如果*.lastUpdated这样的文件很多,则使用专门的批处理脚本统一清理。

② 内部损坏情况:删除损坏的jar包重新下载。

11. TCP三次握手和四次挥手?

置位概念:根据TCP的包头字段,存在3个重要的标识ACK、SYN、FIN
ACK:表示验证字段
SYN:位数置1,表示建立TCP连接
FIN:位数置1,表示断开TCP连接

三次握手过程说明:

① 由客户端发送建立TCP连接的请求报文,其中报文中包含seq序列号,是由发送端随机生成的,并且将报文中的SYN字段置为1,表示需要建立TCP连接。(SYN=1,seq=x,x为随机生成数值)
② 由服务端回复客户端发送的TCP连接请求报文,其中包含seq序列号,是由回复端随机生成的,并且将SYN置为1,而且会产生ACK字段,ACK字段数值是在客户端发送过来的序列号seq的基础上加1进行回复,以便客户端收到信息时,知晓自己的TCP建立请求已得到验证。(SYN=1,ACK=x+1,seq=y,y为随机生成数值)这里的ack加1可以理解为是确认和谁建立连接。
③ 客户端收到服务端发送的TCP建立验证请求后,会使自己的序列号加1表示,并且再次回复ACK验证请求,在服务端发过来的seq上加1进行回复。(SYN=1,ACK=y+1,seq=x+1)

四次挥手过程说明:

① 客户端发送断开TCP连接请求的报文,其中报文中包含seq序列号,是由发送端随机生成的,并且还将报文中的FIN字段置为1,表示需要断开TCP连接。(FIN=1,seq=x,x由客户端随机生成)

② 服务端会回复客户端发送的TCP断开请求报文,其包含seq序列号,是由回复端随机生成的,而且会产生ACK字段,ACK字段数值是在客户端发过来的seq序列号基础上加1进行回复,以便客户端收到信息时,知晓自己的TCP断开请求已经得到验证。(FIN=1,ACK=x+1,seq=y,y由服务端随机生成)
③ 服务端在回复完客户端的TCP断开请求后,不会马上进行TCP连接的断开,服务端会先确保断开前,所有传输到A的数据是否已经传输完毕,一旦确认传输数据完毕,就会将回复报文的FIN字段置1,并且产生随机seq序列号。(FIN=1,ACK=x+1,seq=z,z由服务端随机生成)
④ 客户端收到服务端的TCP断开请求后,会回复服务端的断开请求,包含随机生成的seq字段和ACK字段,ACK字段会在服务端的TCP断开请求的seq基础上加1,从而完成服务端请求的验证回复。(FIN=1,ACK=z+1,seq=h,h为客户端随机生成)
至此TCP断开的4次挥手过程完毕

12. Ojbect有几个方法? 

9个

  • toString()
  • equals()
  • hashCode()
  • getClass()
  • clone()
  • finalize()
  • wait()
  • notify()
  • notifyAll() 

13. 谈谈你对spring的理解?

在不同的语境中,Spring 所代表的含义是不同的。下面我们就分别从“广义”和“狭义”两个角度,对 Spring 进行介绍。

广义的 Spring:Spring 技术栈

广义上的 Spring 泛指以 Spring Framework 为核心的 Spring 技术栈。

经过十多年的发展,Spring 已经不再是一个单纯的应用框架,而是逐渐发展成为一个由多个不同子项目(模块)组成的成熟技术,例如 Spring Framework、Spring MVC、SpringBoot、Spring Cloud、Spring Data、Spring Security 等,其中 Spring Framework 是其他子项目的基础。

这些子项目涵盖了从企业级应用开发到云计算等各方面的内容,能够帮助开发人员解决软件发展过程中不断产生的各种实际问题,给开发人员带来了更好的开发体验。

狭义的 Spring:Spring Framework

狭义的 Spring 特指 Spring Framework,通常我们将它称为 Spring 框架。

Spring 框架是一个分层的、面向切面的 Java 应用程序的一站式轻量级解决方案,它是 Spring 技术栈的核心和基础,是为了解决企业级应用开发的复杂性而创建的。

Spring 有两个最核心模块: IoC 和 AOP。

IoC:Inverse of Control 的简写,译为“控制反转”,指把创建对象过程交给 Spring 进行管理。

AOP:Aspect Oriented Programming 的简写,译为“面向切面编程”。AOP 用来封装多个类的公共行为,将那些与业务无关,却为业务模块所共同调用的逻辑封装起来,减少系统的重复代码,降低模块间的耦合度。另外,AOP 还解决一些系统层面上的问题,比如日志、事务、权限等。

14. spring IOC 与 AOP 怎么理解?

IoC 是 Inversion of Control 的简写,译为“控制反转”,它不是一门技术,而是一种设计思想,是一个重要的面向对象编程法则,能够指导我们如何设计出松耦合、更优良的程序。

Spring 通过 IoC 容器来管理所有 Java 对象的实例化和初始化,控制对象与对象之间的依赖关系。我们将由 IoC 容器管理的 Java 对象称为 Spring Bean,它与使用关键字 new 创建的 Java 对象没有任何区别。

IoC 容器是 Spring 框架中最重要的核心组件之一,它贯穿了 Spring 从诞生到成长的整个过程

AOP全称:面向切面编程

系统是由许多不同的组件所组成的,每一个组件各负责一块特定功能。除了实现自身核心功能之外,这些组件还经常承担着额外的职责。例如日志、事务管理和安全等其他的核心服务经常融入到自身具有核心业务逻辑的组件中去。这些系统服务经常被称为横切关注点,因为它们会跨越系统的多个组件。

当我们需要为分散的对象引入公共行为的时候,OOP则显得无能为力。也就是说,OOP允许你定义从上到下的关系,但并不适合定义从左到右的关系。例如日志功能。

日志代码往往水平地散布在所有对象层次中,而与它所散布到的对象的核心功能毫无关系。

在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。

AOP:将程序中的交叉业务逻辑(比如安全,日志,事务等),封装成一个切面,然后注入到目标对象(具体业务逻辑)中去。AOP可以对某个对象或某些对象的功能进行增强,比如对象中的方法进行增强,可以在执行某个方法之前额外的做一些事情,在某个方法执行之后额外的做一些事情

15. spring bean的生命周期?

  1. bean对象创建(调用无参构造器)
  2. 给bean对象设置属性
  3. bean的前置处理器(初始化之前)
  4. bean对象初始化(需在配置bean时指定初始化方法)
  5. bean的后置处理器(初始化之后)
  6. bean对象就绪可以使用
  7. bean对象销毁(需在配置bean时指定销毁方法)
  8. IOC容器关闭

16. spring的设计模式有哪些?分别怎么应用的?

单例模式(Singleton):Spring 的 IoC 容器默认使用单例模式管理对象,确保在应用程序中只有一个实例被创建和共享。
工厂模式(Factory):Spring 使用工厂模式创建和管理对象,通过配置文件或注解来指示容器创建和配置对象。
依赖注入模式(Dependency Injection):Spring 的核心特性之一是依赖注入,它通过控制反转(Inversion of Control)
        将对象之间的依赖关系委托给容器管理。这样可以实现松耦合、易于测试和可维护的代码。

观察者模式(Observer):Spring 的事件机制使用观察者模式,允许应用程序中的组件发布和订阅事件。这样可以实现组件之间的解耦和通信。
代理模式(Proxy):Spring AOP(面向切面编程)使用了代理模式,通过动态代理技术实现横切关注点的模块化。
        Spring AOP 可以通过动态代理在目标方法前后添加额外的行为,例如事务管理、日志记录等。

模板方法模式(Template Method):Spring 的 JdbcTemplate 和 HibernateTemplate 等模板类使用了模板方法模式,将一些通用的操作封装到模板方法中,而具体的实现由子类来提供。
适配器模式(Adapter):Spring 的适配器模式常见于 Spring MVC 中的处理器适配器(HandlerAdapter),它将不同类型的处理器适配到统一的处理器接口上。
策略模式(Strategy):Spring 的 Validator 接口和其实现类使用了策略模式,允许应用程序根据需要选择不同的验证策略。

17. spring循环依赖问题

两种注入方式对循环依赖的影响?

官方解释https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-dependency-resolution循环依赖如果主要使用构造函数注入,则有可能创建无法解析的循环依赖场景(circular dependency scenario)。

例如:类A通过构造函数注入需要类B的实例,类B通过构造函数注入需要类A的实例。如果为类A和类B配置bean以相互注入,Spring IoC容器将在运行时检测此循环引用,并抛出BeanCurrentlyInCreationException。

  1. 一种可能的解决方案是编辑某些类的源代码,由setter而不是构造函数进行配置。
  2. 或者,避免构造函数注入,只使用setter注入。
  3. 换句话说,虽然不推荐,可以使用setter注入来配置循环依赖项(circular dependencies)。与典型情况(没有循环依赖)不同,bean A和bean B之间的循环依赖迫使一个bean在完全初始化之前注入另一个bean(典型的鸡和蛋场景)

循环依赖项(circular dependencies)真实存在的


三个缓存

  • 一级缓存 singletonObjects --> ConcurrentHashMap(256) 保存经历了完整生命周期的对象(先实例化再初始化)

  • 二级缓存 earlysingletonObjects --> HashMap(16) 存放早期暴露出来的Bean对象,Bean的生命周期未结束

  • 三级缓存  singletonFactories   -->HashMap(16) 存放可以生成Bean的工厂

四个方法    

  • getSingleton() 从缓存中获取bean对象

  • createBean() 创建bean对象

  • populateBean() 初始化bean对象

  • addSingleton() 将bean保存到缓


1. 第一次: 从一级缓存中寻找a对象
    第二次: 先从一级缓存中  再去三级缓存中找
2. 创建a对象
3. 将a保存到三级缓存中

        3.1 将a 保存到三级缓存
        3.2 从二级缓存移除
        3.3 注册到容器中
4. 初始化a的时候发现需要b对象 则去按照a 刚才的执行逻辑去创建b对象
        结果: b保存到了三级缓存
5. 初始化b 
        目前a已经存在于三级缓存中了,所以在初始化的时候就不需要创建 直接获取
         5.1 先从一级缓存 发现没有
         5.2 再从二级缓存中获取,发现还没有
         5.3 最后从三级缓存中获取a 并将a从三级移到二级
6. 初始b结束之后,将b对象从三级缓存直接移动一级缓存
7. 继续完成a的初始化动作
        从一级缓存获取b对象完成 将a对象从二级缓存移动一级缓存


1. Spring创建bean主要分为两个步骤,创建原始bean对象,接着去为bean    对象填充属性和初始化
2. 每次创建bean之前都会先去一级缓存中查,因为是单列,只能有一个
3. 当创建A的原始对象之后,会放在三级缓存中,然后填充属性,这时候需要    依赖B,接着创建B,B实例化的时候需要A,于是先去一级缓存中查,没有,    再查二级缓存,没有,再查三级缓存拿到了A,然后把A放到二级缓存中并删    除三级缓存中的A
4. B初始化完毕,将自己放入一级缓存,此时B中的A依然是创建状态,接着创建A,此时A可以直接从一级缓存中获取B,然后完成创建,将自己放入一级缓存

问题1:为什么构造器注入属性无法解决循环依赖问题?

​由于spring中的bean的创建过程为先实例化 再初始化(在进行对象实例化的过程中不必赋值)将实例化好的对象暴露出去,供其他对象调用,然而使用构造器注入,必须要使用构造器完成对象的初始化的操作,就会陷入死循环的状态

问题2:一级缓存能不能解决循环依赖问题? 不能

​在三个级别的缓存中存储的对象是有区别的 一级缓存为完全实例化且初始化的对象 二级缓存实例化但未初始化对象 如果只有一级缓存,如果是并发操作下,就有可能取到实例化但未初始化的对象,就会出现问题

问题3:二级缓存能不能解决循环依赖问题?

理论上二级缓存可以解决循环依赖问题,但是需要注意,为什么需要在三级缓存中存储匿名内部类(ObjectFactory),原因在于 需要创建代理对象 eg:现有A类,需要生成代理对象 A是否需要进行实例化(需要) 在三级缓存中存放的是生成具体对象的一个匿名内部类,该类可能是代理类也可能是普通的对象,而使用三级缓存可以保证无论是否需要是代理对象,都可以保证使用的是同一个对象,而不会出现,一会儿使用普通bean 一会儿使用代理类

18. spring bean 作用域  默认是什么?

默认是单例

19. spring springmvc springboot 区别和关系?

spring是一个IOC容器,用来管理Bean,使用依赖注入实现控制反转,可以很方便的整合各种框架,提供AOP机制弥补OOP的代码重复问题、更方便将不同类不同方法中的共同处理抽取成切面、自动注入给方法执行,比如日志、异常等

springmvc是spring对web框架的一个解决方案,提供了一个总的前端控制器Servlet,用来接收请求,然后定义了一套路由策略(url到handle的映射)及适配执行handle,将handle结果使用视图解析技术生成视图展现给前端

springboot是spring提供的一个快速开发工具包,让程序员能更方便、更快速的开发spring+springmvc应用,简化了配置(约定了默认配置),整合了一系列的解决方案(starter机制)、redis.mongodb.es,可以开箱即用

20. spring 默认的事务传播机制是什么?

REQUIRED 

事务的传播行为一般发生在事务嵌套的场景中。如:有一个事务的方法里面调用了另外一个有事务的方法。这时会产生事务边界控制问题。即两个方式是各自作为事务提交还是内层事务合并到外层事务一起提交。

传播机制含义
REQUIRED默认值,支持当前事务,如果没有事务会创建一个新的事务
SUPPORTS支持当前事务,如果没有事务的话以非事务方式执行
MANDATORY支持当前事务,如果没有事务抛出异常
REQUIRES_NEW创建一个新的事务并挂起当前事务
NOT_SUPPORTED以非事务方式执行,如果当前存在事务则将当前事务挂起
NEVER以非事务方式进行,如果存在事务则抛出异常
NESTED如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则进行与REQUIRED类似的操作

21. SpringBoot 自动装配原理?

① SpringBoot启动的时候加载主配置类,开启了自动配置功能@EnableAutoConfiguration

② @EnableAutoConfiguration 作用:利用EnableAutoConfigurationlmportSelector给容器中导入一些组件

Springboot在启动的时候会调用run方法,run方法会执行refreshContext()方法刷新容器,会在类路径下找到springboot-boot-autoconfigure/springboot-boot-autoconfigure.jar/META-INF/spring-factories候选文件,该文件中记录中众多的自动配置类,容器会根据我们是否引入依赖是否书写配置文件的情况,将满足条件的Bean注入到容器中,于是就实现了springboot的自动装配  

22. SpringBoot 常用注解?

@SpringBootApplication
@EnableAutoConfiguration
@AutoConfigurationPackage
@Import()
//条件判断注解
@AutoConfiguration
@ConditionalOnClass
@ConditionalOnMissingBean

23. cookie 与 session 区别是什么?

① 对象不同

cookie:是针对每个网站的信息,每个网站只能对应一个,其他网站无法访问,这个文件保存在客户端,每次您拨打相应网站,浏览器都会查找该网站的 cookies,如果有,则会将该文件发送出去。cookies文件的内容大致上包括了诸如用户名、密码、设置等信息。

session:是针对每个用户的,只有客户端才能访问,程序为该客户添加一个 session。session中主要保存用户的登录信息、操作信息等等。此 session将在用户访问结束后自动消失(如果也是超时)。

② 存储数据大小不同

cookie:一个 cookie存储的数据不超过3K。

session:session存储在服务器上可以任意存储数据。当 session存储数据太多时,服务器可选择进行清理。

③ 生命周期不同

cookie:cookie的生命周期当浏览器关闭的时候就消亡了,cookie的生命周期是累计的,从创建时就开始计时,30min后cookie生命周期结束。

session:session的生命周期是间隔的,从创建时开始计时如在30min内没有访问session,那么session生命周期就被销毁。

④ 存储位置不同

cookie:cookie数据保存在客户端。

session:session数据保存在服务器端。

⑤ 数据类型不同

两者都是key-value结构,但针对value的类型是有差异的。

cookie:value只能是字符串类型。

session:value是object类型。

⑥ 安全性不同

cookie不是很安全,别人可以分析存放在本地的COOKIE并进行COOKIE欺骗,如果主要考虑到安全应当使用session。

24. 重定向与转发区别? 

  • 转发的URl的地址不会发生变化重定向的地址会发生变化
  • 转发的request的内置对象是可以使用的而重定向的request内置对象不可以使用
  • 转发的跳转速度比重定向的速度快
  • 转发是request的请求,重定向是response的请求,也就是说转发是客户端响应重定向是服务器响应
  • 重定向:以前的request中存放的变量全部失效,并进入一个新的request作用域转发:以前的request中存放的变量不会失效,就像把两个页面拼到了一起

25. Java 反射原理? 

  • 正射就是由类模板生成了一个一个new出来的具体实例对象;
  • 反射就是给你一个具体的实例对象,可以反向倒查来自于哪一个类模板,并扫描类模板定义的各种东西。
  • 反射百分之90就是和注解结合使用,就通过某个具体实例对象获得它的类模板,通过反射获取对应注解的对应值;这一基操使用就能达到增强。

在Java中,反射机制主要是通过三个类实现的,它们分别是Class类、Constructor类和Method类。

  • Class类:它是反射机制的核心类,通过它可以获得类的属性和方法,实例化对象等。Java中每个类都有一个与之对应的Class对象。
  • Constructor类:它代表类的构造函数,可以用来创建实例对象。
  • Method类:它代表类的方法,可以用来执行类的方法。
  • 反射的核心:JVM在运行时才动态加载类或者调用方法以及访问属性,不需要事先(比如编译时)知道运行对象是什么?
  • 类的加载:Java反射机制是围绕Class类展开的首先要了解类的加载机制!
  • 加载机制:JVM使用ClassLoader将字节码文件,即class文件加载到方法区内存中,ClassLoader类根据类的完全限定名加载类并返回一个Class对象

Java反射机制的原理主要是通过Class类来实现的。Class类是Java中反射机制的核心类,它可以在运行时动态地获取一个类的信息。Class类的实例对象可以通过三种方式获取:

(1)使用Class.forName()方法获取Class对象。Class.forName()方法接受一个字符串参数,该参数为完整类名,它将返回该类的Class对象。

(2)使用类名.class获取Class对象。例如:String.class。

(3)使用对象.getClass()方法获取Class对象。例如:String str = "hello",则str.getClass()将返回String类的Class对象。

26. 对象的内存布局?

① 在HotSpot虚拟机中,对象在堆内存中的存储布局可以划分为三个部分:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)

② 对象内部结构分为:对象头、实例数据、对齐填充(保证8个字节的倍数)。

③ 数组对象与普通对象的内存结构区别在于数组的对象头里面多了一个数组的长度。

1.对象头分为对象标记(markOop)和类元信息(klassOop),类元信息存储的是指向该对象类元数据(klass)的首地址。

2.在64位系统中,Mark Word占了8个字节,类型指针占了8个字节,一共是16个字节

 

27. Hashmap的原理

1.存取无序的
2.键和值位置都可以是null,但是键位置只能是一个null
3.键位置是唯一的,底层的数据结构控制键的
4.jdk1.8前数据结构是:链表 + 数组  jdk1.8之后是 : 链表 + 数组  + 红黑树
5.阈值(边界值) > 8 并且数组长度大于64,才将链表转换为红黑树,变为红黑树的目的是为了高效的查询。

在jdk1.7之前是数组+链表,1.8之后是数组+链表+红黑树,数组中的每个元素都是链表结构,链表中的每个节点就是一个node对象,用来存放kv键值对的值,hashmap的默认初始长度是16,最大长度是2的30次方.

在hashmap中有两个重要的方法: 

  • put()方法:

让重算(原来的哈希值和右移16位的哈希值做按位异或运算)后的哈希值和数组的长度-1做按位与运算,计算出数组元素的下标,如    果这个位置上没有元素,创建一个node对象存入hash值、kv值和next为null的指针,将node存入这个下标,如果有元素,遍历链表,先比较    两个key重算后的hash值是否相同,如果相同,调用equsql方法比较,如果equals结果位true,则覆盖。如果equasls结果为false,新增一个node对象(或者说建立单向链表),新增后判断当前是否达到树化(红黑树)条件。完成插入操作后++modCount(数组的修改次数,替换node元素的value不算),自增后的值大于扩容阈值则触发自动扩容机制reSize()
扩容:
当hashmap中元素个数超过数组长度*加载因子时,就把数组扩大一倍,首先创建一个新的空数组,长度是原数组的2倍,遍历原数组,把所有的元素重新Hash到新数组。为什么要重新Hash呢?因为长度扩大以后,Hash的规则也随之改变。

树化条件:
链表中元素的个数超过TREEIFY_THRESHOLD = 8个并且数组的长度要大于等于MIN_TREEIFY_CAPACITY = 64

  • get()方法:    

get()方法和put方法类似,也是根据计算后的哈希值去数组的长度-1做按位与运算找出下标,然后遍历下标对应链表中的key进行    equals比较,为ture就把元素取出来。

为什么转化红黑树:

当产生hash冲突时会形成链表,当数据多了冲突多了链表越来越长,    造成链化,本来时间复杂度为O(1)的变成了O(n),此时查询变成耗    时操作(在链表中获取数据需要遍历链表,时间复杂度时O(n),红黑    树相当于平衡二叉树(任意节点左右子树的高度相差最大为1)是二    分的查找,时间复杂度是O(logn),效率更高。在我们remove()    数据的时候,当对应下标中的元素个数等于6时,会树退化,变成链表。

28. Hashmap与HashTable区别

he HashMap class is roughly equivalent to Hashtable, except that it is unsynchronized and permits nulls.

  • HashTable 是线程安全的 不允许空键空值
  • HashMap  是线程不安全的 允许空键空值

29. 什么是java的多态?

父类的引用指向子类的实例

30. 拦截器和过滤器区别?

① 出身不同

过滤器来自于 Servlet而拦截器来自于 Spring 框架

② 触发时机不同

请求的执行顺序是:请求进入容器 > 进入过滤器 > 进入 Servlet > 进入拦截器 > 执行控制器(Controller),所以过滤器会先执行,然后才会执行拦截器,最后才会进入真正的要调用的方法。

③ 实现不同

我们在上面实现过滤器的时候就会发现,当我们要执行下一个过滤器或下一个流程时,需要调用 FilterChain 对象的 doFilter 方法进行回调执行。

过滤器的实现是基于方法回调的而拦截器是基于动态代理(底层是反射)实现的

④ 支持的项目类型不同

过滤器是 Servlet 规范中定义的,所以过滤器要依赖 Servlet 容器,它只能用在 Web 项目中而拦截器是 Spring 中的一个组件,因此拦截器既可以用在 Web 项目中,同时还可以用在 Application 或 Swing 程序中。

⑤ 使用的场景不同

因为拦截器更接近业务系统,所以拦截器主要用来实现项目中的业务判断的,比如:登录判断、权限判断、日志记录等业务。

而过滤器通常是用来实现通用功能过滤的,比如:敏感词过滤、字符集编码设置、响应数据压缩等功能。

31. 四大函数式接口?

Function接口:泛型设置方法参数类型和返回值类型

Predicate:断言型接口,返回boolean值

Consumer:消费者函数接口:accept(Object)函数,传入参数,不返回任何

Supplier:提供者函数接口,无传参,有返回值!

32. 将ArrayList中满足条件的遍历删除,你会怎么删除?

使用迭代器删除。

因为ArrayList本身不是线程安全的,在使用迭代器遍历查询的时候,会有一个检查机制,来确保一个线程在遍历的时候,其他线程不会删除该集合中的元素。

33. 如果用到for循环去删,你会怎么做?

fori 删除(顺序会漏删,倒序可正常)

这是因为ArrayList本身是个数组,在进行遍历的时候,如果删除某个元素,则后续的元素需要整体往前移动,当循环到i=1的时候,找到了第一个delete字符串,然后删除,第2个delete以及之后的字符串会向前移动,这个时候第2个delete就在数组的1位置了。当循环到i=2的时候,正好把原来位置的delete给略过去了,导致漏删。

34. Hash函数value值去重,你会怎么做?(你用hashset去重,怎么保留map结构呀?)

方法1.将key和value对调去重后再对调回来。

方法2.创建新的HashMap自带的containsValue做个循环判断value是否存在,不存在就put添加。

MySQL 

1. 数据库的三范式是什么? 

第一范式:强调的是列的原子性,即数据库表的每一列都是不可分割的原子数据项。

第二范式:要求实体的属性完全依赖于主关键字。所谓完全依赖是指不能存在仅依赖主关键字一部分的属性。

第三范式:任何非主属性不依赖于其它非主属性。

2. 事务ACID四大特性是什么?

① Atomicity(原子性):

一个事务(transaction)中的所有操作,或者全部完成,或者全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被恢复(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。即,事务不可分割、不可约简。
② Consistency(一致性):

在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设约束、触发器、级联回滚等。
③ Isolation(隔离性):

数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括读未提交(Read uncommitted)读提交(read committed)可重复读(repeatable read)串行化(Serializable)
④ Durability(持久性):

事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。

3. 事务的三大并发问题:脏读、不可重复读、幻读?

脏读:简单来说,就是一个事务读取到了另一个事务未提交的数据。

不可重复读:就是说,比如在A事务中进行多次相同的查询,B事务在A事务多次查询之间修改对应表中的数据,导致A事务多次读取的结果不一致。(针对更新和删除操作)

幻读:举例来说,就是A事务将表中'性别'列的值都更改为1,B事务在A事务修改之后又添加了一条记录,其中'性别'的值为0,回过来A再查询所有的记录时会发现有一条记录的'性别'为0,这种情况就是所谓的幻读。(针对添加操作)

4. @Transactional默认对哪种异常进行处理 

补充:Spring的事务注解@Transactional,默认在运行时异常(RuntimeException)或Error会抛异常。

我们可以设置所有异常时事务都进行回滚~

@Service
@Transactional(rollbackFor = java.lang.Exception.class)

5. 事务的四大隔离级别?

MySQL默认级别:可重复读级别MySQL在这个级别也可以避免幻读

数据库事务的隔离性:

  • 数据库系统必须具有隔离并发运行各个事务的能力,使它们不会相互影响,避免各种并发问题。
  • 一个事务与其他事务隔离的程度称为隔离级别
  • 数据库规定了多种事务隔离级别,不同隔离级别对应不同的干扰程度,隔离级别越高,数据—致性就越好,但并发性越弱。 

隔离级别

描述

Read Uncommitted

(读取未提交内容)

允许A事务读取其他事务未提交和已提交的数据。会出现脏读、不可重复读、幻读问题 。

Read Committed

(读取已提交内容)

只允许A事务读取其他事务已提交的数据。可以避免脏读,但仍然会出现不可重复读、幻读问题 

Repeatable Read

(可重复读)

确保事务可以多次从一个字段中读取相同的值。在这个事务持续期间,禁止其他事务对这个字段进行更新。可以避免脏读和不可重复读。但是幻读问题仍然存在注意: mysql中使用了MVCC多版本控制技术,在这个级别也可以避免幻读。 

Serializable

(可串行化)

确保事务可以从一个表中读取相同的行,相同的记录。在这个事务持续期间,禁止其他事务对该表执行插入、更新、删除操作。所有并发问题都可以避免,但性能十分低下。 

6. 事务的七大传播行为?

传播行为描述
PROPAGATION_REQUIRED支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。
PROPAGATION_SUPPORTS支持当前事务,如果当前没有事务,就以非事务方式执行。
PROPAGATION_MANDATORY支持当前事务,如果当前没有事务,就抛出异常。
PROPAGATION_REQUIRES_NEW新建事务,如果当前存在事务,把当前事务挂起。
PROPAGATION_NOT_SUPPORTED以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
PROPAGATION_NEVER以非事务方式执行,如果当前存在事务,则抛出异常。
PROPAGATION_NESTED如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则新建一个事务

 

7. 乐观锁和悲观锁的区别?

悲观锁(Pessimistic Lock), 顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。

乐观锁(Optimistic Lock), 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量。Redis就是利用这种check-and-set机制实现事务的。
数据库的乐观锁需要自己实现,在表里面添加一个 version 字段,每次修改成功值加 1,这样每次修改的时候先对比一下,自己拥有的 version 和数据库现在的 version 是否一致,如果不一致就不修改,这样就实现了乐观锁。

8. 相比UUID,为什么使用雪花算法?

  • 雪花算法使用时间戳+机器码+毫秒内的序号生成一个长度为19 的 10进制的数值型的字符串
  • 过滤性强、按时间顺序递增、毫秒内生成的id不重复、全局唯一
  • UUID: 全局唯一 但是生成的是32长度 16进制数字组成的字符串 过滤性差 无序

雪花算法是生成分布式全局唯一id的算法,它会得到一个64位长度的long类型的数据,其中的64位数据由四部分组成,第一个bit位是一个符号位,因为id不会是负数,所以默认为0,接着用41位来表示毫秒单位的时间戳,再用10个bit位来表示工作机器的id,最后用12个bit位来表示递增的序列号,然后把这64个bit位拼接成一个long类型的、长度为19的10进制数字,这就是雪花算法的实现。

9. 影响sql性能的常见情况

  • 数据过多:分库分表(根据微服务划分库、按照地域或时间分表存储、按照数据的特定字段对分库数量求余)
  • 关联了太多的表,太多join:允许表出现冗余字段减少联查 SQL优化
  • 没有充分利用到索引:索引建立
  • 服务器调优及各个参数设置:调整my.cnf

10. B+Tree与B-Tree 的区别

B-树的关键字和记录是放在一起的,叶子节点可以看作外部节点,不包含任何信息;

B+树的非叶子节点中只有关键字和指向下一个节点的索引,记录只放在叶子节点中。树的高度会更矮胖,IO次数也会更少。

在B-树中,越靠近根节点的记录查找时间越快,只要找到关键字即可确定记录的存在;而B+树中每个记录的查找时间基本是一样的,都需要从根节点走到叶子节点,而且在叶子节点中还要再比较关键字。从这个角度看B-树的性能好像要比B+树好,而在实际应用中却是B+树的性能要好些。因为B+树的非叶子节点不存放实际的数据,这样每个节点可容纳的元素个数比B-树多,树高比B-树小,这样带来的好处是减少磁盘访问次数。尽管B+树找到一个记录所需的比较次数要比B-树多,但是一次磁盘访问的时间相当于成百上千次内存比较的时间,因此实际中B+树的性能可能还会好些,而且B+树的叶子节点使用指针连接在一起,方便顺序遍历(例如查看一个目录下的所有文件,一个表中的所有记录等),这也是很多数据库和文件系统使用B+树的缘故。  

11. 为什么说B+树比B-树更适合实际应用中操作系统的文件索引和数据库索引?

① B+树的磁盘读写代价更低   

B+树的内部结点并没有指向关键字具体信息的指针。因此其内部结点相对B 树更小。如果把所有同一内部结点的关键字存放在同一盘块中,那么盘块所能容纳的关键字数量也越多。一次性读入内存中的需要查找的关键字也就越多。相对来说IO读写次数也就降低了。

② B+树的查询效率更加稳定   

由于非终结点并不是最终指向文件内容的结点,而只是叶子结点中关键字的索引。所以任何关键字的查找必须走一条从根结点到叶子结点的路。所有关键字查询的路径长度相同,导致每一个数据的查询效率相当。

12. 索引分类

  1. 主键索引:设定为主键后数据库会自动建立索引,innodb为聚簇索引

  2. 单值索引:即一个索引只包含单个列一个表可以有多个单列索引

  3. 唯一索引:索引列的值必须唯一,但允许有空值

  4. 复合索引:即一个索引包含多个列

什么是索引? 

索引是一种基于B+树,用于快速查询和检索数据的数据结构。

聚簇索引?

把键值和行记录保存在一起,找到键值就找到了数据,一张表只能有一个,通常就是主键。

聚集索引一般是表中的主键索引primary,如果表中没有没有指定的话,会选择表中第一个不允许为null的唯一索引,都没有的话,会自动生成一个会递增的隐藏主键。

非聚簇索引 ?

辅助索引

把键值和主键保存在一起,需要二次查询(回表),一张表可以有多个。

13. 哪些情况需要创建索引

  • 主键自动建立唯一索引

  • 频繁作为查询条件的字段应该创建索引

  • 查询中与其它表关联的字段,外键关系建立索引

  • 单键/组合索引的选择问题, 组合索引性价比更高

  • 查询中排序的字段,排序字段若通过索引去访问将大大提高排序速度

  • 查询中统计或者分组字段

14. 哪些情况不要创建索引

  • 表记录太少

  • 经常增删改的表或者字段。

    Why:提高了查询速度,同时却会降低更新表的速度,如对表进行INSERT、UPDATE和DELETE。因为更新表时,MySQL不仅要保存数据,还要保存一下索引文件

  • Where条件里用不到的字段不创建索引

  • 过滤性不好的不适合建索引

  • 有大量重复数据的列上。

15. explain 是什么?能干什么?怎么用?

① 是什么?

使用EXPLAIN关键字可以模拟优化器执行SQL查询语句,从而知道MySQL是如何处理你的SQL语句的分析你的查询语句或是表结构的性能瓶颈。

② 能干什么? 

  • 表的读取顺序

  • 哪些索引可以使用

  • 数据读取操作的操作类型

  • 哪些索引被实际使用

  • 表之间的引用

  • 每张表有多少行被物理查询

③ 怎么用?

explain + SQL语句

16. 索引优化原则

  1. 不在索引列上做任何操作(计算、函数、(自动or手动)类型转换),会导致索引失效而转向全表扫描

  2. like以通配符开头('%abc...')mysql索引失效会变成全表扫描的操作

  3. mysql 在使用不等于(!=或者<>)的时候无法使用索引会导致全表扫描

  4. is not null 也无法使用索引,但是is null是可以使用索引的

  5. 字符串不加单引号索引失效

17. 组合索引原则

  1. 全值匹配我最爱

  2. 符合最左原则:不跳过索引中的列

  3. 如果where条件中是OR关系,加索引不起作用

  4. 存储引擎不能使用索引中范围条件右边的列

18. 针对16题和17题索引建议

  1. 对于单键索引,尽量选择针对当前query过滤性更好的索引

  2. 在选择组合索引的时候,当前Query中过滤性最好的字段在索引字段顺序中,位置越靠前越好。

  3. 在选择组合索引的时候,尽量选择可以能够包含当前query中的where字句中更多字段的索引

  4. 在选择组合索引的时候,如果某个字段可能出现范围查询时,尽量把这个字段放在索引次序的最后面

  5. 书写sql语句时,尽量避免造成索引失效的情况

19. 关联索引优化建议

  1. 保证被驱动表的join字段已经创建了索引

  2. left/right join 时,选择小表作为驱动表,大表作为被驱动表。

  3. inner join 时,mysql会自己帮你把小结果集的表选为驱动表,对被驱动表连接字段创建索引。(5.6已经优化掉了,5.5需要手动编写)

  4. 子查询尽量不要放在被驱动表,有可能使用不到索引。

  5. 能够直接多表关联的尽量直接关联,不用子查询。(减少查询的趟数)

20. 索引什么时候会失效?导致索引失效的情况?

  • 对于组合索引,不是使用组合索引最左边的字段,则不会使用索引
  • 以%开头的like查询如%abc,无法使用索引;非%开头的like查询如abc%,相当于范围查询,会使用索引
  • 查询条件中列类型是字符串,没有使用引号,可能会因为类型不同发生隐式转换,使索引失效
  • 判断索引列是否不等于某个值时
  • 对索引列进行运算
  • 查询条件使用or连接,也会导致索引失效

21. exist和in的区别? 

exists用于对外表记录做筛选。exists会遍历外表,将外查询表的每一行,代入内查询进行判断。当exists里的条件语句能够返回记录行时,条件就为真,返回外表当前记录。反之如果exists里的条件语句不能返回记录行,条件为假,则外表当前记录被丢弃。

select a.* from A awhere exists(select 1 from B b where a.id=b.id)

in是先把后边的语句查出来放到临时表中,然后遍历临时表,将临时表的每一行,代入外查询去查找。

select * from Awhere id in(select id from B)

子查询的表比较大的时候,使用exists可以有效减少总的循环次数来提升速度;

当外查询的表比较大的时候,使用in可以有效减少对外查询表循环遍历来提升速度。

22. truncate、delete与drop区别?

相同点:

  1. truncate和不带where子句的delete、以及drop都会删除表内的数据。

  2. droptruncate都是DDL语句(数据定义语言),执行后会自动提交。

不同点:

  1. truncate 和 delete 只删除数据不删除表的结构;drop 语句将删除表的结构被依赖的约束、触发器、索引;
  2. 一般来说,执行速度: drop > truncate > delete。

23. having和where区别?

  • 二者作用的对象不同,where子句作用于表和视图,having作用于组。
  • where在数据分组前进行过滤,having在数据分组后进行过滤。

24. 什么是MySQL主从同步?

主从同步使得数据可以从一个数据库服务器复制到其他服务器上,在复制数据时,一个服务器充当主服务器(master),其余的服务器充当从服务器(slave)。

因为复制是异步进行的,所以从服务器不需要一直连接着主服务器,从服务器甚至可以通过拨号断断续续地连接主服务器。通过配置文件,可以指定复制所有的数据库,某个数据库,甚至是某个数据库上的某个表。

25. 为什么要做主从同步?

  1. 读写分离,使数据库能支撑更大的并发。
  2. 在主服务器上生成实时数据,而在从服务器上分析这些数据,从而提高主服务器的性能。
  3. 数据备份,保证数据的安全。

26. 乐观锁和悲观锁是什么?

数据库中的并发控制是确保在多个事务同时存取数据库中同一数据时不破坏事务的隔离性和统一性以及数据库的统一性。乐观锁和悲观锁是并发控制主要采用的技术手段。

  • 悲观锁:假定会发生并发冲突,在查询完数据的时候就把事务锁起来,直到提交事务。实现方式:使用数据库中的锁机制。
  • 乐观锁:假设不会发生并发冲突,只在提交操作时检查是否数据是否被修改过。给表增加version字段,在修改提交之前检查version与原来取到的version值是否相等,若相等,表示数据没有被修改,可以更新,否则,数据为脏数据,不能更新。实现方式:乐观锁一般使用版本号机制或CAS算法实现。

27. 建立索引命令?

① 普通索引

1. create index indexName on tableName (columnName(可设长度))
2. alter table tableName add index indexName(columnName(可设长度))
3. create table tableName(
    index indexName (columnName(可设长度))
)

② 联合索引

# 唯一索引数据不可
create unique index indexName on tableName(columnName1,columnName2,..,..)
 

28. MySQL索引关键字?

index(普通索引)、unique(组合索引)、primary(主键索引)

29. 窗口函数

mysql从8.0开始支持窗口函数,什么是窗口?

可以理解为记录集合,窗口函数就是在满足某种提交的记录集合上执行的特殊函数,按照功能划分,可以把mysql支持的窗口函数分为如下几类:

名称方法
序号函数row_number() / rank()/dense_rank() 主要解决排序/排名
分布函数percent_rank() /cume_dist()
前后函数lag() /lead()
头尾函数first_val() /last_val()
其他函数nth_value() /nfile()

窗口函数基本用法:函数名([expr]) over 字句

over是关键字,用来指定函数执行的窗口范围

30. 窗口函数 row_number()、rank()、dense_rank() 区别?

(1) row_number:依次排序,不会出现相同排名(如1,2,3)

(2) rank():出现相同的排名时,跳跃排序(如1,1,3)

(3) dense_rank():出现相同排序时,连续排序(如1,1,2)

31. partition by 和 group by 区别?

partition by 与 group by不同之处在于前者返回的是分组里的每一条数据,并且可以对分组数据进行排序操作。后者只能返回聚合之后的组的数据统计值的记录。

32. 使用concat拼接 输出百分比符号

concat(sum(case when s_score>=0 and s_score<60 then 1 else 0 end )/count(*)*100,'%') '[0-60]',

Redis

1. 谈一谈 Redis 持久化两种方式?

RDB(Redis DataBase):指定的时间间隔能对你的数据进行快照存储。

RDB方式要比AOF方式更加的高效。RDB的缺点是最后一次持久化后的数据可能丢失。

AOF(Append Of File):AOF持久化以日志的形式记录服务器所处理的每一个写、删除操作,查询操作不会记录,以文本的方式记录,可以打开文件看到详细的操作记录。

AOF和RDB同时开启,系统默认取AOF的数据。备份机制更稳健,丢失数据概率更低;比起RDB占用更多的磁盘空间。

官方推荐两个都启用。如果对数据不敏感,可以选单独用RDB。如果只是做纯内存缓存,可以都不用。

2. 说说Redis哈希槽的概念?

Redis集群没有使用一致性hash,而是引入了哈希槽的概念,Redis集群有16384个哈希槽,每个key通过CRC16校验后对16384取模来决定放置哪个槽,集群的每个节点负责一部分hash槽。

3. Redis有哪些常用数据类型? 

string、list、set、hash、zset

string:SDS 简单动态字符串
list:数据少的时候:ziplist  数据多:quickList   eg: 消息队列 评价
set:基于hash表的字典(dict)                    eg:去重
zset:基于hash表的字典 + 跳跃表(排序)          eg: 排行榜
hash::数据少的时候:ziplist 数据多:HashTable   eg:购物车

----------都用于处理是大数据量的信息-------------------------
Bitmaps:

  • 实际上也是一个支持位运算的string 
  • 以位为单位的数组,数组的每一个单元只能存储0或者1   ---->  布隆过滤器

HyperLogLog:统计网站

  • uv: user view 
  • pv: page view
  • IP: 

Geospatial: 

4. Redis是单线程还是多线程?为什么能支持访问量和高并发? 

单线程

从Redis 6.0版本开始,针对处理网络请求过程采用了多线程,而数据的读写命令,仍然是单线程处理的。

① 完全基于内存,绝大部分请求是纯粹的内存操作,非常快速。数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1)。

② 采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗。

③ 使用I/O多路复用模型,非阻塞IO。

④ 采用哈希槽离概念。

什么是I/O多路复用模型?

  • I/O多路复用是指利用单个线程来同时监听多个Socket ,并在某个Socket可读、可写时得到通知,从而避免无效的等待,充分利用CPU资源。目前的I/O多路复用都是采用的epoll模式实现,它会在通知用户进程Socket就绪的同时,把已就绪的Socket写入用户空间,不需要挨个遍历Socket来判断是否就绪,提升了性能。
  • 其中Redis的网络模型就是使用I/O多路复用结合事件的处理器来应对多个Socket请求,比如,提供了连接应答处理器、命令回复处理器,命令请求处理器;
  • 在Redis6.0之后,为了提升更好的性能,在命令回复处理器使用了多线程来处理回复事件,在命令请求处理器中,将命令的转换使用了多线程,增加命令转换速度,在命令执行的时候,依然是单线程。

5. Redis主从复制集群中配置哨兵能起到什么作用?

在master宕机后自动从slave中选举新的master。

在slave宕机再重新启动后自动建立主从关系。 

6. Redis什么时候去删除过期的数据?

redis过期删除策略通常有三种:定时删除,定期删除,惰性删除

redis使用的是:“定期删除+惰性删除”
① 定时删除:

在设置某个key 的过期时间同时,我们创建一个定时器,让定时器在该过期时间到来时,立即执行对其进行删除的操作。

② 定期删除:

默认的100ms就会随机抽一些设置过期的时间的key, 去检查是否过期,过期了就删了。

③ 惰性删除:

不主动删,我懒,等查询了我看看你过期没,过期就删了还不给你返回,没过期该怎么样就怎么样。

7. 缓存的目的、场景、技术、方式、过程?

目的:

    ① 提高并发能力:降低每个请求的响应时间
    ② 保护数据库:很多请求可以直接从缓存中获取数据,就不用从mysql数据库获取数据。

场景:读多写少:读的并发量高;写的频率较低;
    ① 读比较频繁
    ② 写的频率较低

技术:

    ① redis:内存型。单线程,支持更多的数据类型(kv v可以是字符串、list、hash、set、zset)
    ② memcache:内存型。多线程,性能更高(kv v只能是字符串)

方式:

   ① 注解:@EnableCache @Cacheable
   ② 编码方式:查询缓存,如果缓存命中则直接返回;如果缓存没有命中则查询数据库后再放入缓存。

过程:

   ① 先查缓存 

   ② 再查数据库,放入缓存

8. 基于redis实现缓存:客户端选择?

(1)jedis
(2)SpringData-Redis:
    ① RedisTemplate有四类序列化器
       xml     性能最差    最多内存    可读性较差
       json      稍好           较多           最好
      string    较好           最少           最好    将来如果保存对象或者集合,需要手动序列化(json)
       jdk        最高          比较多         没有

    ② StringRedisTemplate(String)

9. 缓存数据一致性问题:mysql redis ?

1. 双写模式
① 下策先写redis:写入redis成功(新)--> 写入mysql失败(旧)--> 导致数据不一致
② 上策先写mysql:写入mysql成功 --> 写入redis成功(新)--> mysql提交失败(旧)--> 数据不一致

2. 失效模式 
① 下策先删redis:
    A请求:先删redis --> 写入mysql  --> 提交事务mysql(新)
    B请求:查询该条数据 --> redis是空的 --> 查询mysql(旧)--> 放入redis缓存(旧)
    现mysql新;redis旧  数据不一致
② 上策先写mysql: 
    A请求:先写mysql --> 后删redis(空)--> 提交事务mysql(新)
    B请求:查询该条数据 --> 查询redis是空的 --> 查询mysql(旧)--> 把旧数据放入redis缓存(旧)
    出现mysql新;redis旧  数据不一致

失效模式+定时删除==> 延迟双删
3. 双删模式
思路:
    1.先写mysql 
    2.删除redis缓存(常规操作,立马生效)
    3.提交事务
    4.删除redis缓存(保险操作,异步方式、延迟方式,最终一致性)
    异步双删:提交事务之前发送异步消息
    延迟双删:发送延迟消息(30s)异步删除缓存    

 4. 中间件:canal  阿里开源

数据同步问题:简介
1.双写模式
       1.先写redis:下策
       2.先写mysql:上策

2.失效模式
       1.先删redis:下策
       2.后删redis:上策

3.双删模式:
      异步双删:
      延迟双删:
      1.先写mysql 
      2.后删redis 
      3.事务提交之后:延迟队列,AOP思想
      4.异步再删redis

4.中间件:canal maxwell 

10. 缓存常见读问题:读 

① 缓存穿透:大量请求同时访问不存在的数据,由于数据不存在缓存中可能就没有该数据的缓存,此时所有请求直达mysql数据库。导致mysql服务器宕机
    解决方案:(布隆+null值缓存)数据即使为null也缓存(缓存时间不能太长,一般不超过5min);布隆过滤器解决。

② 缓存击穿:一个热点的key过期,此时大量请求访问该数据,就会直达mysql服务器,导致mysql服务器宕机
     解决方案:分布式锁 

③ 缓存雪崩:缓存时间相同,导致大量缓存数据同时过期,此时所有请求直达mysql数据库。导致mysql服务器宕机
    解决方案:给缓存时间添加随机值

        

  • 缓存穿透:恶意行为,不存在的数据
  • 缓存击穿:正常访问行为,存在的数据,只是数据过期
  • 对比介绍
  • 缓存雪崩:缓存的数据量
  • 缓存击穿:一个热点数据

缓存常见读问题:简介
① 缓存穿透:大量请求访问不存在的数据,由于数据不存在,缓存无法命中,所有请求直达数据库。
     解决:缓存为null的数据(缓存时间不宜过长);布隆过滤器

② 缓存雪崩:由于缓存时间相同,导致大量缓存数据同时过期,所有请求就会直达数据库。
     解决:给缓存时间添加随机值

③ 缓存击穿:一个热点的key失效,此时大量请求就会直达数据
     解决:分布式锁。

11. 布隆过滤器介绍?

① 概念:Bloom Filter,一个牺牲精确度换取空间和时间效率的一种概率性算法。
② 作用:用于判断一个数据是否存在
③ 结构:

  1. 二进制数组
  2. 一系列的hash函数

④ 特点:

  1. 判定一个数据存在,不一定存在
  2. 判定一个数据不存在,一定不存在
  3. 删除困难:CountingBloomFilter
  4. 存储的不是原始数据,保证原始数据的安全性

⑤ 影响精确度:

  1. 增加hash函数的个数:hash函数的个数越多精确度就越高,但是性能就会越低
  2. 增加二进制数组的长度:二进制数组越长精确度越高,但是占用更多内存

⑥ 实现:
     1. google guava常用工具库,类似lang3 业内标杆
         BloomFilter.create/put/mightContain()
     2. redisson 分布式版本的布隆过滤器
         RBloomFilter bf = this.redissonClient.getBloomFilter("name")
         bf.tryInit()/add/contains
     3. redis插件 
     4. 基于bitmap数据模型

⑦ 实现:解决缓存穿透:

  1. 项目启动时,就会初始化布隆过滤器,并加载初始化数据(三级分类)
  2. 缓存封装时,先查询缓存如果缓存无法命中,再通过布隆过滤判断数据是否存在,如果不存在则直接返回null即可

12. 布隆过滤器如何进行数据同步?

只能重新生成把旧的布隆过滤器覆盖掉

  1. 实时性要求较高:pms中发送消息给MQ,然后首页工程获取消息重新生成
  2. 实时性要求不高:使用定时任务,比如:每隔一天重新生成一个新的布隆过滤器把旧的覆盖掉  

13. 布隆过滤器由于服务器重启等原因 导致数据丢失如何处理?

  1. 持久化布隆过滤器:可以将布隆过滤器中的数据持久化到磁盘中,例如使用文件或数据库等方式进行存储,以便在服务器重启后能够重新加载数据。但是,这样做会增加额外的存储和读写成本。

  2. 增量式添加数据:可以在每次向布隆过滤器中添加新数据时,将数据同时写入到持久化存储中,以便在重启后能够重新加载数据。但是,这样做会增加添加数据的时间复杂度。

  3. 定期备份数据:可以定期备份布隆过滤器中的数据,例如每天备份一次,以便在服务器重启后能够重新加载数据。但是,这样做会增加额外的备份和恢复成本。

  4. 通过其他数据结构进行辅助:可以将布隆过滤器和其他数据结构结合使用,例如使用哈希表进行数据存储,以便在服务器重启后能够重新加载数据。但是,这样做会增加额外的存储和查询成本。

针对布隆过滤器数据丢失问题,可以采用多种方式进行解决,具体选择哪种方式需要根据具体的业务需求和实际情况进行权衡和选择。

MQ消息队列 

1. MQ五大消息模型

简单消息模型(simple)、工作消息模型(work)、订阅消息模型(广播-fanout、定向-direct、主题-topic)

simple消息模型 一个生产者 一个消费者 一个队列

work消息模型 一个生产者 多个消费者 一个队列

     能者多劳: 消费者性能高的可以多消费消息

fanout消息模型 一个生产者 一个交换机 多个队列 多个消费者

direct消息模型 一个生产者 一个交换机 多个队列 多个消费者(队列绑定交换机时需要指定routingkey)

topic消息模型: 开发中使用最多 一个生产者 一个交换机 多个队列 多个消费者(队列绑定交换机使用的routingkey可以使用通配符 *通配一级任意多个字符 #匹配任意多级任意多个字符)

2. 如何保证消息的可靠性传输/如何处理消息丢失问题?

考虑维度

分析

生产者

原因:网络中断

解决1:可以使用rabbitmq提供的事务功能

      就是生产者发送数据之前开启rabbitmq事务(channel.txSelect),然后发送消息,如果消息没有成功被rabbitmq接收到,那么生产者会收到异常报错,此时就可以回滚事务(channel.txRollback),然后重试发送消息;如果收到了消息,那么可以提交事务(channel.txCommit)此方法会严重降低系统的吞吐量,性能消耗太大

解决2:生产者开发confirm模式之后

      你每次写的消息都会分配一个唯一的id,然后如果写入了rabbitmq中,rabbitmq会给你回传一个ack消息,告诉你说这个消息ok了。如果rabbitmq没能处理这个消息,会回调你一个nack接口,告诉你这个消息接收失败,你可以重试。而且你可以结合这个机制自己在内存里维护每个消息id的状态,如果超过一定时间还没接收到这个消息的回调,那么你可以重发。

rabbitMQ本身

原因:MQ宕机

解决:开启rabbitmq的持久化

     就是消息写入之后会持久化到磁盘,哪怕是rabbitmq自己挂了,恢复之后会自动读取之前存储的数据,一般数据不会丢。除非极其罕见的是,rabbitmq还没持久化,自己就挂了,可能导致少量数据会丢失的,但是这个概率较小。

     设置持久化有两个步骤,第一个是创建queue的时候将其设置为持久化的,这样就可以保证rabbitmq持久化queue的元数据,但是不会持久化queue里的数据;第二个是发送消息的时候将消息的deliveryMode设置为2,就是将消息设置为持久化的,此时rabbitmq就会将消息持久化到磁盘上去。必须要同时设置这两个持久化才行,rabbitmq哪怕是挂了,再次重启,也会从磁盘上重启恢复queue,恢复这个queue里的数据。

     而且持久化可以跟生产者那边的confirm机制配合起来,只有消息被持久化到磁盘之后,才会通知生产者ack了,所以哪怕是在持久化到磁盘之前,rabbitmq挂了,数据丢了,生产者收不到ack,你也是可以自己重发的。

消费者

原因:刚消费到,还没处理,结果进程就挂了

解决:

    用rabbitmq提供的ack机制,简单来说,就是你关闭rabbitmq自动ack,可以通过一个api来调用就行,然后每次你自己代码里确保处理完的时候,再程序里ack一把。这样的话,如果你还没处理完,不就没有ack?那rabbitmq就认为你还没处理完,这个时候rabbitmq会把这个消费分配给别的consumer去处理,消息是不会丢的。

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

(1)数据要写库操作,最好先根据主键查一下,如果这数据都有了,就不再执行insert操作,可以update。

(2)写入redis,redis的set操作天然幂等性。

(3)你需要让生产者发送每条数据的时候,里面加一个全局唯一的id,类似订单id之类的东西,然后你这里消费到了之后,先根据这个id去比如redis里查一下,之前消费过吗?

如果没有消费过,你就处理,然后这个id写redis。

如果消费过了,那你就别处理了,保证别重复处理相同的消息即可。(防止订单重复提交)

4. 为什么要使用MQ?

核心:解耦,异步,削峰填古

解耦A 系统发送数据到 BCD 三个系统,通过接口调用发送。如果 E 系统也要这个数据呢?那 如果 C 系统现在不需要了呢?A 系统负责人几乎崩溃......A 系统跟其它各种乱七八糟的系统严重耦 合,A 系统产生一条比较关键的数据,很多系统都需要 A 系统将这个数据发送过来。如果使用 MQ,A 系统产生一条数据,发送到 MQ 里面去,哪个系统需要数据自己去 MQ 里面消费。如果新 系统需要数据,直接从 MQ 里消费即可;如果某个系统不需要这条数据了,就取消对 MQ 消息的消 费即可。这样下来,A 系统压根儿不需要去考虑要给谁发送数据,不需要维护这个代码,也不需要 考虑人家是否调用成功、失败超时等情况。 就是一个系统或者一个模块,调用了多个系统或者模块,互相之间的调用很复杂,维护起来很麻 烦。但是其实这个调用是不需要直接同步调用接口的,如果用 MQ 给它异步化解耦。

异步A 系统接收一个请求,需要在自己本地写库,还需要在 BCD 三个系统写库,自己本地 写库要 3ms,BCD 三个系统分别写库要 300ms、450ms、200ms。最终请求总延时是 3 + 300 + 450 + 200 = 953ms,接近 1s,用户感觉搞个什么东西,慢死了慢死了。用户通过浏览器发起请 求。如果使用 MQ,那么 A 系统连续发送 3 条消息到 MQ 队列中,假如耗时 5ms,A 系统从接受一 个请求到返回响应给用户,总时长是 3 + 5 = 8ms。
削峰减少高峰时期对服务器压力。

5. MQ有什么优缺点?

优点上面已经说了,就是在特殊场景下有其对应的好处,解耦、异步、削峰
缺点有以下几个:
系统可用性降低 系统引入的外部依赖越多,越容易挂掉。万一 MQ 挂了,MQ 一挂,整套系统崩溃,你不就完了?
系统复杂度提高 硬生生加个 MQ 进来,你怎么保证消息没有重复消费?怎么处理消息丢失的情况? 怎么保证消息传递的顺序性?问题一大堆。
一致性问题 A 系统处理完了直接返回成功了,人都以为你这个请求就成功了;但是问题是,要是 BCD 三个系统那里,BD 两个系统写库成功了,结果 C 系统写库失败了,咋整?你这数据就不一致了。

6. 怎么避免消息堆积?

1.使用工作模型,搭建消费者集群,配合能者多劳(公平分发)充分发挥每台服务器的性能

        spring.rabbitmq.listener.simple.prefetch=1 
2.使用多线程消费,充分发挥多核CPU的优势
        spring.rabbitmq.listener.concurrency=6

7. 怎么避免消息丢失、确保消息不丢失? 

 1.生产者确认机制
            spring.rabbitmq.publisher-confirm-type=none/simple/correlated
            spring.rabbitmq.publisher-returns=true
            this.rabbitTemplate.setConfirmCallback();
            this.rabbitTemplate.setReturnCallback();

2.消息持久化:交换机持久化、队列持久化、消息持久化
            默认:交换机、队列、消息都是持久化的,如果不想持久化:设置队列或者交换机的durable属性为false

3.消费者确认机制:自动ACK 手动ACK 

            spring.rabbitmq.listener.simple.acknowledge-mode=none/auto/manual
            channel.basicAck/basicNack/basicReject()

4.备份交换机(Fanout)进入备份队列(多个)

3. 怎么保证有序消费?队列本身就是有序的。

1.使用独占排他队列,只有一个消费者
2.使用单线程消费

4. 怎么保证幂等性消费?

1.代码层面保证幂等性
2.结合事务保证幂等性
3.通过messageId唯一标识 + redis的setnx指令

Nginx

1. Nginx有哪些基本功能? 

(1)反向代理(2)负载均衡(3)动静分离

Docker

1. Docker的作用?

① 更快速的应用交付和部署

② 更便捷的升级和扩缩容

③ 更简单的系统运维

④ 更高效的计算资源利用

Linux

1. 在Linux中如何通过命令查看指定进程和端口号的信息?

比如查看mysql进程:ps -ef | grep mysql

比如查看mysql占用的端口号:netstat -anp | grep mysql

2. Linux命令中的管道要使用什么符号?它是如何工作的?

管道符号是“|”,表示前面命令的输出作为后面命令的输入。

3. pwd命令的作用是什么?

打印当前所在目录的绝对路径

4. mkdir -p /aaa/bbb/ccc命令中-p表示什么?

表示一次性创建多层目录

5. cp -r /aaa/bbb/ccc /aaa/bbb/ddd命令中-r表示什么?

表示当前复制操作的目标是目录

6. linux命令你用过哪些?

ls:对于目录,该命令列出该目录下的所有子目录与文件。对于文件,将列出文件名以及其他信息

cd:改变工作目录。将当前工作目录改变到指定的目录下

tail -f filename:查看文件内容(可以查日志)

top:查看系统内存使用情况-性能分析工具

free:显示系统中物理上的空闲和已用内存,还有交换内存,同时,也能显示被内核使用的缓冲和缓存。

netstat -anp | grep 端口号 :查看端口号

find :用来在指定目录下查找文件

man :帮助我们查看命令参数

ps -ef|grep 进程名:用来列出系统中当前正在运行哪那些进程

微服务

1. SpringBoot 和 SpringCloud 开发中常用注解?

@SpringBootApplication

SpringBoot启动类注解

@Configuration

声明一个类为配置类

@RestController

ResponseBody + @Controller合在一起的作用

@MapperScan

是spring用于批量注入mybatis映射器(DAO接口)的注解

@ServletComponentScan

SpringBootApplication 上使用@ServletComponentScan 注解后
Servlet可以直接通过@WebServlet注解自动注册
Filter可以直接通过@WebFilter注解自动注册
Listener可以直接通过@WebListener 注解自动注册

@Bean

被 @Bean标注的方法会生成一个由 Spring 容器管理的 bean。

@EnableTransactionManagement

 它是springboot的事务管理注解

@EnableDiscoveryClient

@EnableDiscoveryClient 和@EnableEurekaClient 都是能够让注册中心能够发现,扫描到改服务。

@EnableFeignClients

告诉框架扫描所有使用注解@FeignClient定义的feign客户端,并把feign客户端注册到IOC容器中。

@FeignClient

用于创建声明是API接口,该接口是RESTFUL风格的。Feign被设计成插拔式的,可注入其他组件和Feign一起使用。最典型的是如果Ribbon可用,Feign会和Ribbon相结合进行负载均衡。

2. 简单介绍一下SpringCloud中的服务熔断机制。

微服务系统中由于调用链很长,所以一处出现问题或超时,会逐渐蔓延到整个系统。

为了避免这样的问题,熔断机制要求给被调用的方法准备备用方案,一旦目标方法调用失败则调用备用方法返回相同类型的返回值。

备用方法要求入参、返回值和原方法一致。

3. 你如何理解负载均衡? 

在负载沉重时由多台服务器分担负载。

具体工作时由负载均衡服务器基于特定算法将每一个具体请求分配到具体服务器上。

4. 什么是方法的远程调用? 

服务提供方将方法暴露在网络上,消费方通过网络调用目标方法。

这个过程中底层需要消费方发送请求,接收响应。

5. 在分布式架构应用中注册中心起到了什么作用? 

注册中心中存储服务提供方所暴露的服务的详细信息,借助注册中心中存储的信息就能够通过远程方法调用框架实现声明式调用(像调用本地方法一样调用远程方法)。

6. 项目的单一架构和分布式架构有什么本质区别?

单一架构应用打包后是一个war包,在一台Tomcat上运行。

分布式架构应用打包后是很多个war包,分别在不同Tomcat上运行。

7. 分布式架构有什么好处?

模块化、组件化程度更高,让项目更容易开发、维护和分工。

也是高内聚、低耦合的一种体现。

分布式系统中的每一个组件可以单独部署到一个Tomcat服务器上,独占软硬件资源,在必要的时候还可以将某一个组件配置集群,所以分布式架构能够提升性能。

8. 分布式和集群这两个概念有什么区别?

分布式系统在多台服务器上运行不同模块,集群在多台服务器上运行相同模块

9. 为什么说Ribbon是一种客户端负载均衡? 

因为是使用Ribbon时是从consumer出发在Eureka中查询对应的微服务信息,决定从集群中访问哪一个实例。

10. SpringCloud组件有哪些?

注册中心: 管理服务的ip+端口列表

        eureka: 需要我们自己搭服务

        nacos:直接打包成一个可执行程序

远程调用:

        ribbon:提供了负载均衡策略+restTemplate实现远程访问

        feign:整合ribbon 以接口的方式封装远程访问的配置

        openfeign:基于feign进行了封装 以注解的方式简化了feign的使用

        开发中基本都是使用openfeign

熔断降级:

        hystrix: 断路器 实现熔断 +降级;无法针对慢调用配置熔断降级

        sentinel: 阿里开源的产品;可以细粒度配置每个资源/各种场景 触发熔断降级+限流

分布式链路追踪:

        sleuth+zipkin:

        skywalking:

网关:

        zuul/zuul2:

        gateway: 基本都是使用它

配置中心:抽取服务的动态配置参数管理

        springcloud config:

        nacos:2.x以后 支持管理配置的维度较多 使用逐渐变多

        apollo:携程开源的 支持灰度发布


nacos:注册中心+配置中心(重点是使用)

openfeign: 远程调用

sentinel:熔断降级+限流

sleuth:链路追踪

gateway:网关

① 组成: 断言  路由  过滤器

② 原理: 网关会将请求进行匹配 将满足指定断言规则的请求经过各个过滤器的过滤之后路由到指定的位置

③ 在gateway工程中进行如下配置
        断言: 路径断言  /pms/service  还有域名断言
        路由:
                uri: lb(load balance:负载均衡)
                uri: https://example.org
                uri: ws://example.org    
        过滤器:
                全局过滤器 
                局部过滤器

④ 负载均衡:配置路由使用的就是lb
⑤ 过滤请求:解决跨域问题
⑥ 统一鉴权:自定义全局过滤器实现sso
⑦ 全局熔断:没用

11. hystrix断路器作用?

① 服务远程访问时 有失败的可能会导致服务雪崩效应。

​② 路器可以在远程访问失败时返回兜底数据,给前端友好提示。

③ ​高并发时断路器可以快速失败 返回兜底数据 回收线程。

三种状态:
关闭:远程访问正常、没有达到hystrix阈值时(10s 20个请求超过一半失败) 断路器关闭
全开:达到hystrix阈值时(10s 20个请求超过一半失败) 断路器打开
           打开后的20s内 所有的请求都不在放行给目标服务,直接返回兜底方法结果

半开:达到hystrix阈值时(10s 20个请求超过一半失败),20s后,每过5秒放行一个请求
          如果请求执行失败,断路器一直处于半开状态
          如果请求执行成功,断路器关闭

12. 服务熔断和服务降级的区别?

① 服务降级:不管在什么情况下,服务降级的流程都是先调用正常的方法,再调用fallback的方法。也就是服务器繁忙,请稍后再试,不让客户端等待并立刻返回一个友好提示。

② 服务熔断:假设服务宕机或者在单位时间内调用服务失败的次数过多,即服务降级的次数太多,那么则服务熔断。并且熔断以后会跳过正常的方法,会直接调用fallback方法,即所谓“服务熔断后不可用”。类似于家里常见的保险丝,当达到最大服务访问后,会直接拒绝访问,拉闸限电,然后调用服务降级的fallback方法,返回友好提示。

13. 集群、分布式、SOA、微服务的概念及区别?

集群、分布式、SOA和微服务都是分布式架构的一种实现方式,它们的应用场景和设计理念有所不同,但都可以通过横向扩展、负载均衡、容错和异地多活等技术手段提高系统的可用性和性能。他们区别如下:

架构方式

区别

集群

不同服务器部署同一套应用服务对外提供访问,实现服务的负载均衡或者互备(热备,主从等),指同-种组件的多个实例,形成的逻辑上的整体。单个节点可以提供完整服务。集群是物理形态

分布式

服务的不同模块部署在不同的服务器上,单个节点不能提供完整服务,需要多节点协调提供服务(也可以是相同组件部署在不同节点、但节点间通过交换信息协作提供服务),分布式强调的是工作方式

SOA

面向服务的架构,一种设计方法,其中包含多个服务,服务之间通过相互依赖最终提供一 系列的功能。一个服务通常以独立的形式存在于操作系统进程中。各个服务之间通过网络调用 经典的技术使用栈 dubbo+zookeeper 或者 Eureka+ OpenFeign

微服务

在SOA 基础上做的升华,微服务架构强调的一个重点是业务需要彻底的组件化和服务化,原有的单个业务系统会拆分为多个可以独立开发、设计、运行的小应用。这些小应用之间通过服务完成交互和集成,采用RESTAPI通信 经典的技术使用栈: springboot+springcloud nacos+openFeign

14. Feign与openFeign的区别?

① 他们底层都是内置了Ribbon,去调用注册中心的服务。

② Feign是Netflix公司写的,是SpringCloud组件中的一个轻量级RESTful的HTTP服务客户端,是SpringCloud中的第一代负载均衡客户端。

③ OpenFeign是SpringCloud自己研发的,在Feign的基础上支持了Spring MVC的注解,如@RequesMapping等等。是SpringCloud中的第二代负载均衡客户端。

④ Feign本身不支持Spring MVC的注解,使用Feign的注解定义接口,调用这个接口,就可以调用服务注册中心的服务。

⑤ OpenFeign的@FeignClient可以解析SpringMVC的@RequestMapping注解下的接口,并通过动态代理的方式产生实现类,实现类中做负载均衡并调用其他服务。

⑥ feign已不在维护,openfeign维护频繁。

15. Eureka Zookeeper nacos 区别?

比较维度具体区别
功能特性Nacos提供了服务注册和发现、配置管理和元数据管理等一系列功能,同时还支持多种协议和语言的接入;Eureka主要提供了服务注册和发现的功能,并支持高可用部署;Zookeeper则提供了分布式协调和一致性算法的支持,同时也可以用作服务注册和发现的中间件
数据一致性Nacos采用了基于Raft协议的一致性算法来保证数据的一致性;Eureka采用了基于CAP原则的异步复制机制来保证数据的一致性;而Zookeeper则采用了基于Zab协议的原子广播机制来保证数据的一致性
管理界面Nacos提供了用户友好的Web界面,可以直观地管理服务和配置信息;Eureka提供了基本的管理界面,但不如Nacos的界面友好;Zookeeper则没有提供用户界面
社区支持Nacos和Eureka都有较大的社区支持和活跃度,同时都有较好的文档和资料;Zookeeper也有较大的社区支持和应用场景,但近年来活跃度和发展速度不如Nacos和Eureka

nacos: Raft协议的一致性算法(网络好的时候是cp 网络不好ap)
zookeeper: Zab协议的原子广播机制(相当于是cp 强调的是一致性)
Eureka: CAP原则的异步复制机制 (AP)
        C: 一致性
        A: 可用性
        P: 分区容忍性 

了解和扩展 

Zab协议和Raft协议都是一类一致性协议,主要用于在分布式系统中保持数据一致性。下面是它们之间的几个区别:
1. Leader Election(领导者选举)

Zab协议中,选主(Leader)是通过一个叫做广播(Broadcast)的机制来实现的。当集群中的任何一个服务器启动或在运行中的Leader宕机时,都会广播一个提议(Proposal),希望能够成为新的Leader。然后,其他服务器会回复是否接受该提议。如果有超过半数的服务器接受该提议,那么该提议就被批准,提议的服务器就成为新的Leader。

Raft协议中,选主是通过Leader选举(Leader Election)机制来实现的。当集群中的Leader宕机时,其他服务器会等待一段随机时间后开始发起选举。然后,服务器会通过互相发送投票(Vote)请求来选举新的Leader。当有一个服务器得到了超过半数的投票时,就成为新的Leader。

2. Log Replication(日志复制)

Zab协议和Raft协议在日志复制方面都采用了类似的机制。在Zab协议中,Leader会将新的操作复制到所有的Follower上。在Raft协议中,Leader会将新的操作追加到自己的日志中,然后向其他Follower发送AppendEntries请求,Follower收到请求后,将自己的日志与Leader的日志进行比较,并根据比较结果进行更新。

3. Membership Changes(成员变更)

Zab协议和Raft协议在成员变更方面也有不同。在Zab协议中,成员变更是通过崩溃恢复(Crash Recovery)来实现的。如果一个新的服务器加入集群或者一个服务器从集群中离开,那么这个服务器的状态将通过崩溃恢复来获取或者删除。

在Raft协议中,成员变更是通过集群配置变更(Cluster Configuration Change)来实现的。如果一个新的服务器加入集群或者一个服务器从集群中离开,那么Leader将会向其他服务器发送一条配置变更请求,其他服务器在接受该请求后,将更新自己的集群配置。

综上所述,Zab协议和Raft协议都是一类一致性协议,它们都采用Leader机制来实现分布式系统中的数据一致性。它们之间的差异主要在于选举机制、日志复制机制和成员变更机制。

16. 说一下springcloud里面hystrix?

  • Netlix开源了Hystrix组件,实现了断路器模式,SpringCloud对这一组件进行了整合。

  • 主要功能分为4大板块隔离、限流、熔断、降级。我们常用的是请求熔断服务降级

  • 涉及注解EnableHystrixHystrixcommand(fallbackMethod="xx")

  • Feign默认集成了Hystrix.。RestTemplate. springCloud GateWay在使用的时候都会考虑集成Hystrix.

  • 服务熔断,指的是服务故障,再让新的请求去访问根本没有意义,这个时候选择暂时断开请求。

  • 服务熔断依靠hystrix的断路器,它有全开、半开、关闭3种状态

  • 服务降级,指断路打开后,为了避免连锁故障,使用fallback方法返回当前不可用的友好提示。

总而言之,hystrix它是为了解决,由于单个服务故障,导致其他依赖服务不可用的“雪崩"效应。比如有服务A,

假设A1.A100都依赖了A,假如A出现了问题,那A1.A100这100个服务也跟着出现了问题

17. Hystrix实现熔断原理是什么?

熔断原理:断路器的工作流程

  • 全开: 当服务调用失败比例超过50% 断路器处于全开状态
  • 半开: 默认5S后会放一个请求过来,如果请求成功,则断路器关闭 否则断路器全开
  • 关闭: 问题修复好之后,断路器关闭,服务正常

① 正常情况下,断路器关闭,服务消费者正常请求微服务。

② 一段时间内,失败率达到一定阈值,断路器将断开,此时不再请求服务提供者,而是只是快速失败的方法(断路方法)。

③ 断路器打开一段时间,自动进入“半开"状态,此时,断路器可允许一个请求方法服务提供者,如果请求调用成功,则关闭断路器,否则继续保持断路器打开状态。

④ 断路器hystrix是保证了局部发生的错误,不会扩展到整个系统,从而保证系统的即使出现局部问题也不会造成系统雪崩。

18. 请求熔断与服务降级区别?

请求熔断:当HystrixCommand请求后端服务失败数量超过一定比例默认50%),断路器会切换到开路状态(Open).这时所有请求会直接失败而不会发送到后端服务.断路器保持在开路状态一段时间后(默认5秒),自动切换到半开路状态(HALF-OPEN).

这时会判断下一次请求的返回情况,如果请求成功,断路器切回闭路状态(CLOSED),否则重新切换到开路状态(OPEN). Hystrix的断路器就像我们家庭电路中的保险丝,一旦后端服务不可用,断路器会直接切断请求链,避免发送大量无效请求影响系统吞吐量,并且断路器有自我检测并恢复的能力。

服务降级:Fallback相当于是降级操作.对于查询操作,我们可以实现一个fallback方法,当请求后端服务出现异常的时候,可以使用fallback方法返回的值fallback方法的返回值一般是设置的默认值或者来自缓存.告知后面的请求服务不可用了,不要再来了

19. 如何解决feign远程调用请求头丢失问题?

  • 原因:消费方通过feign调用生产方接口的时候,feign会发起一个与消费方不同的一个新的请求,导致新的请求头中信息与消费方发起的请求头信息不一致
  • 方法:利用tomcat请求与线程绑定机制 即 spring提供的RequestContextHolder 解决
  • 每次RequestContextHolder,getRequestAttributes获取的都是当前执行的请求线程的requestAttributes,也就获取了当前请求的request而requestAttributes具备ThreadLocal属性,属于线程内变量,各个线程之间互不干扰

JUC

1. wait 和 sleep 的区别?

wait:Object的成员方法需要通过对象调用
           释放锁等待
           必须在synchronized方法中 使用

sleep:Thread的静态方法
              持有锁等待

2. 创建线程的方式?4种方式!

  • 自定义类继承Thread基类 重写run方法
  • 自定义类实现Runnable接口 重写run方法
  • 自定义类实现Callable接口 重写call方法
  • 线程池

所有的线程创建方式 最终都是通过Thread对象的start()方法来启动线程
① 继承Thread:

 

public class MyThread extends Thread
new MyThread().start();

    java是单继承
② 实现Runnable: 

 

class MyRunnable implements Runnable//新建类实现runnable接口
new Thread(new MyRunnable(), name).start() // 使用Rannable实现类创建进程,name是线程名
new Thread(new Runnable() {
    @Override
    public void run() {
 		// 调用资源方法,完成业务逻辑
    }
}, "your thread name").start();

3. ReentrantLock和synchornized区别?

1、synchornized自动加锁释放锁,ReentrantLock手动控制,所以编程时一定要释放锁
2、都可以重入
3、ReentrantLock可以公平也可以不公平,synchornized不公平
4、ReentrantLock可以响应中断(获取锁会超时失败 tryLock ),synchornized不可中断
5、它俩都是独占的悲观的排它锁

4. synchronized 总结

synchronized:
   ①总结:独占的悲观的排他的可重入不可响应中断非公平的锁
   ②用法: 成员方法(锁的是调用方法的对象)、静态方法(类)、代码块(指定对象)
   ③升级过程: 不加锁-> 偏向锁 -> 自旋锁 -> 悲观锁

         -不加锁:加锁的方法只有一个线程访问时,不需要加锁
         -偏向锁:轻量级锁,获取锁的线程较少时而且锁的时间较短,锁偏向第一个线程:A线程获取到锁释放然后又尝试获取锁,B线程也在获取锁,锁优先给A
         -自旋锁:获取锁失败的线程,尝试循环再次获取锁直到成功
                        while(true){ 尝试获取锁 }
                        自旋锁多线程并发时,等待锁的线程继续执行,CPU仍然给他分配时间片,时间片浪费了
                       自旋次数超过10次,自旋锁会升级为悲观锁
         -悲观锁:获取锁失败的线程会挂起到锁对象头中保存(不会被分配时间片)
                       当使用锁的线程释放锁以后,会通知对象头中的挂起的线程们去争抢锁,争抢成功继续执行,失败继续挂起

                       挂起线程和唤醒线程们争抢锁 会有上下文切换的时间损耗

  ④特点:
         -jvm级别的锁,使用简单,自动加锁释放锁
         -可以重入: 持有锁的线程调用另一个需要此锁的方法时可以直接进入执行
         -非公平: 线程按照先来后到的顺序获取锁使用就是公平
         -不可响应中断:线程获取锁时 如果失败会一直阻塞获取锁
             
死锁:
                  线程1:获取A锁的业务代码中再尝试获取另外一个B锁
                  线程2:获取B锁的业务代码中再尝试获取另外一个A锁
             出现死锁的原因:持有锁未释放时尝试获取另一个锁 可能会导致死锁
                  解决:
                     如何查询运行的java程序中是否出现死锁?
                     jps: 可以查看所有运行中的java进程
                     jstack pid:可以查看pid进程的所有线程执行情况,以及是否有死锁

         -独占锁悲观锁

5. ReentrantLock 总结

ReentrantReadWriteLock:可重入的读写锁

ReadLock:读锁
    共享锁,读操作共享
WriteLock:写锁
    独享锁:写操作独享
并发读写操作时,使用读锁对读操作加锁,使用写锁对写操作加锁
    只有并发读时,读锁共享,可以并行执行
    一旦有写操作,写操作优先获取锁,只能有写操作执行,读操作等待锁

适用于读多写少的场景

6. 并发容器和同步容器有哪些?

同步容器:
    Vector、HashTable、Collections中synchornized实现的集合类
并发容器:
    ConcurrentHashMap:读未加锁
    Cow容器:写时复制技术,读未加锁
        CopyOnWriteArrayList
        CopyOnWriteArraySet

7. ConcurrentHashMap实现过程?

在 JDK1.7 中, ConcurrentHashMap 是由 Segment 数组 + HashEntry 组成 ,数据结构上和 HashMap 一样,仍然是数组加链表。

JDK8采用了 CAS + synchronized 来保证并发安全性,放弃原有的 Segment 分段锁 。

1、先判断k-v如果为空抛出异常
2、计算key的hash值: hash
3、遍历table:空数组

第一次添加元素:
3.1 如果table数组为空:初始化table
3.2 继续遍历table:
      n:接收table的长度16
      i = (n - 1) & hash)计算key在数组中
          应该存的索引位置
     f = tabAt(tab, i = (n - 1) & hash)获取
          i再数组中的Node元素对象
     f为空:
3.3 
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {//获取到i位置的元素为空
               //casTabAt:将k-v创建为一个Node对象存入到table的i位置
                //cas原子性操作:硬件级别的
                if (casTabAt(tab, i, null,
                             new Node<K,V>(hash, key, value, null)))
                    //结束循环
                    break;                   // no lock when adding to empty bin
 }

第二次添加元素:
4、遍历table
     n=tab.length
    else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
    4.1 如果上一行代码:判断key所在的索引位置为空,代表key所在索引位置第一次添加元素
          存储步骤和上面3.3一样
    4.2 如果上一行代码:判断key索引位置不为空
5、执行else
       synchronized(f):f 4else if中获取到的key所在索引的头节点Node对象
                最后将k-v创建为Node对象添加到头节点链表的最后(也可能升级为红黑树) 

8. 常见JUC多线程辅助类哪三个?

CountDownLatch: 倒计数器
        多线程并发时,可以以指定条件阻塞多个线程,当倒计数为0时停止阻塞
CyclicBarrier:循环栅栏
        多个倒计数器的组合使用,可以设置多个屏障点,并发执行的多个线程都到达一个屏障点时才可以继续往后执行
Semaphore:信号量
        管理可以重复利用但是数量有限的资源


结合上面的多线程辅助类,可以完成项目中特定的一些业务,例如秒杀:
    同时保证秒杀数据安全和并发性能


Redisson也参考jdk juc包中以上的类 提供了实现类,支持分布式环境
RCountDownLatch
RCyclicBarrier
RSemaphore

9. JUC辅助类解决秒杀问题分析图

10.. CountDownLatch 与 join 方法的区别?

调用一个子线程的 join()方法后,该线程会一直被阻塞直到该线程运行完毕。

而 CountDownLatch 则使用计数器允许子线程运行完毕或者运行中时候递减计数,也就是 CountDownLatch 可以在子线程运行任何时候让 await 方法返回而不一定必须等到线程结束;

另外使用线程池来管理线程时候一般都是直接添加 Runnable 到线程池这时候就没有办法在调用线程的 join 方法了,countDownLatch 相比 Join 方法让我们对线程同步有更灵活的控制。  

11. CyclicBarrier和CountDownLatch的区别?

CountDownLatch的计数器只能使用一次,而CyclicBarrier的计数器可以使用reset()方法重置,可以使用多次,所以CyclicBarrier能够处理更为复杂的场景;

CountDownLatch允许一个或多个线程等待一组事件的产生,而CyclicBarrier用于等待其他线程运行到栅栏位置。

12. callable接口与runnable接口的区别?

相同点:都是接口,都可以编写多线程程序,都采用Thread.start()启动线程

不同点:

  1. 具体方法不同:一个是run,一个是call

  2. Runnable没有返回值;Callable可以返回执行结果,是个泛型

  3. Callable接口的call()方法允许抛出异常;Runnable的run()方法异常只能在内部消化,不能往上继续抛

  4. 它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。

13. 获得多线程的方法几种?

传统的是继承thread类和实现runnable接口

java5以后又有实现callable接口java的线程池获得

14. 传统多线程创建方式的缺点?

① Thread:单继承

② Runnable:解决了需要继承的问题

主线程不能直接获取子线程执行的任务结果,也不能捕获子线程的异常

15. 阻塞队列两种情况?

  • 如果队列满了(有边界),有新的写操作不能写入时,写操作的线程会阻塞挂起,直到队列有新的空间可以存入元素,写线程才会被唤醒执行写操作。
  • 如果队列空了,有新的读操作,读操作线程会被阻塞挂起,直到有新的元素写入到队列中读线程才会被唤醒执行读操作。

16. 为什么需要 BlockingQueue 阻塞队列?

好处是我们不需要关心什么时候需要阻塞线程,什么时候需要唤醒线程,因为这一切,BlockingQueue都给你一手包办了。在concurrent包发布以前,在多线程环境下,我们每个程序员都必须去自己控制这些细节,尤其还要兼顾效率和线程安全,而这会给我们的程序带来不小的复杂度。

17. 线程池的优势?为什么要使用线程池?

线程池做的工作主要是控制运行的线程数量,如果线程数量超过了最大数量,超出数量的线程排队等候,等线程任务执行完毕,再从队列中取出任务来执行。

因为不使用线程池的话,我们会频繁的就行线程的创建和销毁, 极大消耗了cpu性能

18. 线程池的主要特点为:线程复用;控制最大并发数;管理线程

  1. 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的销耗。​​

  2. 提高响应速度。当任务到达时,任务可以不需要等待线程创建就能立即执行。

  3. 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会销耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。


  • 线程池可以执行 runnbale 和 callable 任务
  • 线程池使用了阻塞队列存入不能及时执行的任务

19. 线程如果无限制创建会怎么样?

  • CPU调度性能下降(频繁切换上下文)。
  • 内存溢出:每个线程都通过一个栈结构存储栈数据(基本类型的值、引用类型的地址、本地变量表..),jvm默认为每个线程分配1m的空间。

20. 谈一谈乐观锁和悲观锁?

① 乐观锁: CAS(Compare and Swap 比较并交换的意思)是并发编程中的一种乐观锁的实现 保证原子操作的底层实现。
        所有读都可以执行,写操作需要比较版本号,可以写的线程操作数据后版本号会更新,版本号不一致更新失败

乐观锁问题:

  • 并发读的线程 可能获取的版本号一样,这一组线程只能有一个更新成功,其他的都失败
  • 并发更新失败会导致大量的 CPU计算,影响性能
  • cas的aba问题: 线程修改数据时无法判断数据之前是否被修改过(数据也会当做版本号使用)

② 悲观锁:
        多线程并发时,获取锁的线程才可以执行 

num++ 如何保证多线程操作下原子性?

调用AtomicInteger类incrementAndGet方法不用加锁可以实现安全的递增。

/**
* 在当前值的基础上自动加 1
*
* @return 更新后的值
*/
public final int incrementAndGet() {
    return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}

利用unsafe类提供的compareAndSwapXXX(O,long,oldV,newV)

// CAS 操作
    public final native boolean compareAndSwapInt(Object o, long offset,
                                                  int expected,
                                                  int x);

unsafe了解多少?
            1,获取Unsafe
            2,获取属性偏移量
            3,调用Unsafe类compareAndSwapXXX

21. 乐观锁和悲观锁使用场景?

  • 并发读多写少:乐观锁  读不会加锁,写才会验证版本号
  • 并发写多读少:悲观锁   秒杀  

22. CAS缺点?

  • 开销大在并发量比较高的情况下,如果反复尝试更新某个变量,却又一直更新不成功,会给CPU带来较大的压力
  • ABA问题当变量从A修改为B再修改回A时,变量值等于期望值A,但是无法判断是否修改,CAS操作在ABA修改后依然成功。
  • 不能保证代码块的原子性CAS机制所保证的只是一个变量的原子性操作,而不能保证整个代码块的原子性。

23. ABA问题的解决?

解决原理:不能使用值当做版本号,另外维护一个版本号。

  1. 使用 juc 解决:Atomic类 提供了额外的支持版本号的类!
  2. 使用AtomicStampedReference(版本号原子引用)解决!

AtomicStampedReference 在构建的时候需要一个类似于版本号的 int 类型变量 stamped,每一次针对共享数据的变化都会导致该 stamped 的变化(stamped 需要应用程序自身去负责,AtomicStampedReference并不提供,一般使用时间戳作为版本号)

public static void main(String[] args) {
    AtomicStampedReference<String> r = new AtomicStampedReference<String>("a",1);
    System.out.println("修改前版本号:"+r.getStamp()+" ,值: "+ r.getReference());
    r.compareAndSet("a","b",1,2);
    System.out.println("第一次修改后版本号:"+r.getStamp()+" ,值 "+ r.getReference());
    r.compareAndSet("b","a",1,3);
    System.out.println("第二次修改后版本号:"+r.getStamp()+" ,值 "+ r.getReference());
}

结果:

  • 修改前版本号:1 ,值: a
  • 第一次修改后版本号:2 ,值 b
  • 第二次修改后版本号:2 ,值 b

24. 哪些集合是线程安全?

List:     

  • vector: 同步方法
  • SynchronizedList:同步代码块  迭代器方法未加锁
  • CopyOnWriteArrayList: 写时复制技术实现的

Map: 

  • HashTable
  • ConcurrentHashMap:分段加锁     

25. 如何自定义线程池?

线程池是通过Executor框架实现的,该框架中用到了Executor,ExecutorService,ThreadPoolExecutor这几个类。

线程池7大参数:核心线程数、最大线程数、存活时间、存活时间单位、阻塞队列、线程工厂、拒绝策略

  1. corePoolSize:线程池中的常驻核心线程数

  2. maximumPoolSize:线程池中能够容纳同时 执行的最大线程数,此值必须大于等于1

  3. keepAliveTime:多余的空闲线程的存活时间 当前池中线程数量超过corePoolSize时,当空闲时间达到keepAliveTime时,多余线程会被销毁直到 只剩下corePoolSize个线程为止

  4. unit:keepAliveTime的单位

  5. workQueue:任务队列,被提交但尚未被执行的任务

  6. threadFactory:表示生成线程池中工作线程的线程工厂, 用于创建线程,一般默认的即可

  7. handler:拒绝策略,表示当队列满了,并且工作线程大于 等于线程池的最大线程数(maximumPoolSize)时,如何来拒绝 请求执行的runnable的策略

public class ThreadPoolDemo {

    public static void main(String[] args) {
        // 创建单一线程的连接池
        // ExecutorService threadPool = Executors.newSingleThreadExecutor();
        // 创建固定数线程的连接池
        // ExecutorService threadPool = Executors.newFixedThreadPool(3);
        // 可扩容连接池
        // ExecutorService threadPool = Executors.newCachedThreadPool();
		//创建延时任务的连接池
         //ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(3);
        //executor.scheduleAtFixedRate(()->{
         //   System.out.println("任务正在执行:"+ new Date());
        //},5 , 3 , TimeUnit.SECONDS);
        // 自定义连接池
        ExecutorService threadPool = new ThreadPoolExecutor(2, 5,
                2, TimeUnit.SECONDS, new ArrayBlockingQueue<>(3),
                Executors.defaultThreadFactory(),
                //new ThreadPoolExecutor.AbortPolicy()
                //new ThreadPoolExecutor.CallerRunsPolicy()
                //new ThreadPoolExecutor.DiscardOldestPolicy()
                //new ThreadPoolExecutor.DiscardPolicy()
                new RejectedExecutionHandler() {
                    @Override
                    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
                        System.out.println("自定义拒绝策略");
                    }
                }
        );

        try {
            for (int i = 0; i < 9; i++) {
                threadPool.execute(() -> {
                    System.out.println(Thread.currentThread().getName() + "执行了业务逻辑");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            threadPool.shutdown();
        }
    }
}

26. 线程池工作原理?

  1. 在创建了线程池后,线程池中的线程数为零

  2. 当调用execute()方法添加一个请求任务时,线程池会做出如下判断:

    1. 如果正在运行的线程数量小于corePoolSize,那么马上创建线程运行这个任务;

    2. 如果正在运行的线程数量大于或等于corePoolSize,那么将这个任务放入队列

    3. 如果这个时候队列满了且正在运行的线程数量还小于maximumPoolSize,那么还是要创建非核心线程立刻运行这个任务

    4. 如果队列满了且正在运行的线程数量大于或等于maximumPoolSize,那么线程池会启动饱和拒绝策略来执行

  3. 当一个线程完成任务时,它会从队列中取下一个任务来执行。

  4. 当一个线程无事可做超过一定的时间(keepAliveTime)时,线程会判断:

    如果当前运行的线程数大于corePoolSize,那么这个线程就被停掉。

    所以线程池的所有任务完成后,它最终会收缩到corePoolSize的大小。


拒绝策略介绍:

一般我们创建线程池时,为防止资源被耗尽,任务队列都会选择创建有界任务队列,但种模式下如果出现任务队列已满且线程池创建的线程数达到你设置的最大线程数时,这时就需要你指定ThreadPoolExecutor的RejectedExecutionHandler参数即合理的拒绝策略,来处理线程池"超载"的情况。

ThreadPoolExecutor自带的拒绝策略如下:

  1. AbortPolicy(默认):直接抛出RejectedExecutionException异常阻止系统正常运行

  2. CallerRunsPolicy:“调用者运行”一种调节机制,该策略既不会抛弃任务,也不会抛出异常,而是将某些任务回退到调用者,从而降低新任务的流量。

  3. DiscardOldestPolicy抛弃队列中等待最久的任务,然后把当前任务加人队列中 尝试再次提交当前任务。

  4. DiscardPolicy该策略默默地丢弃无法处理的任务,不予任何处理也不抛出异常。 如果允许任务丢失,这是最好的一种策略。

以上内置的策略均实现了RejectedExecutionHandler接口,也可以自己扩展RejectedExecutionHandler接口,定义自己的拒绝策略、


  • abortPolicy 默认使用 拒绝并抛出异常 一般生产环境使用该策略 目的是一旦监控到有异常,要及时调整线程池参数
  • discardPolicy  拒绝但是不抛出异常
  • discardOldestPolicy 拒绝等待时间最长的任务
  • CallRunsPolicy   调用主线程执行任务    一般在开发环境和测试环境
  • 自定义拒绝策略

27. 介绍JMM?

java跨平台:

  1. java多线程会交给操作系统由cpu调度。
  2. 操作系统有wins、macos、linux。
  3. 但是每个系统的硬件架构、cpu调度等方式可能不同,java跨平台的多线程代码可能会出现执行结果不一致的情况。为了保证java代码在每个平台执行效果一样,java提供了jvm,内存分配cu调度 jvm提供了JMM模型统一内存分配管理。

JMM保证java代码 内存分配、多线程执行效果在不同平台都是一样的。 


JMM规定了内存主要划分为主内存工作内存两种。

  • 主内存:保存了所有的变量。
  • 共享变量:如果一个变量被多个线程使用,那么这个变量会在每个线程的工作内存中保有一个副本,这种变量就是共享变量。
  • 工作内存:每个线程都有自己的工作内存,线程独享,保存了线程用到的变量副本(主内存共享变量的一份拷贝)。工作内存负责与线程交互,也负责与主内存交互。

此处的主内存和工作内存跟JVM内存划分(堆、栈、方法区)是在不同的维度上进行的,如果非要对应起来,主内存对应的是Java堆中的对象实例部分,工作内存对应的是栈中的部分区域,从更底层的来说,主内存对应的是硬件的物理内存,工作内存对应的是寄存器和高速缓存。

   

28. JMM内存模型?

目的是为了保证多线程代码在不同平台执行结果一致。
要求1:
    每个线程的数据都是独立的,工作内存(本地内存)只能自己使用。
要求2:
    每个线程都不能直接操作共享内存中的数据,必须先加载到自己的栈空间进行操作。
基于以上的两点,多线程java代码保证线程安全时,可以保证。


内存模型的三大特性:

  • 原子性: 多线程java代码保证线程安全时 一系列代码是一个整体
  • 可见性: 一个线程的操作可以被另一个线程感知
  • 有序性: 高并发多线程执行时,jmm将指令集交给CPU执行时,为了提高执行效率,可能会进行指令重排(打乱原有的代码的顺序)   指令执行的顺序和代码开发编写的顺序不一致

29. synchronized与lock区别?

相同点:默认都是非公平锁 都支持可重入
区别:
① synchronized 

  • 实现源码是c++  
  • 自动释放锁
  • 性能:在竞争不激烈的情况下,优先考虑使用synchronized

② Lock

  • 是java语言实现
  • 必须手动释放
  • 用场景更加丰富    

30. 线程概念

进程: 正在进行中程序
线程: 程序中一个不可分割的执行单元

31. 线程分类

普通线程 也叫业务线程 也叫 用户线程
守护线程 是为了保护/守护 普通线程能够正常执行的一类线程 eg: GC线程

32. 线程状态 

1. 新建状态(New):线程被创建后就进入的新建状态,Eg:Thread thread = new Thread();
2. 就绪状态(Runnable):线程对象被创建后,start()方法被调用,从而启动该线程,例如thread.statt();处于就绪状态的线程随时可能被CPU调度执行。
3. 运行状态(Running):线程获取到CPU资源进行执行,线程只能从就绪状态进入运行状态。
4. 阻塞状态(Blocking):线程放弃CPU资源暂时停止运行。直到线程进入就绪状态才能转到运行状态。
阻塞状态分三种:
等待阻塞:调用了线程的wait()方法,从而停止运行进入阻塞。
同步阻塞:线程获取synchronized同步锁失败(锁被其他线程占用),进入阻塞状态。
其他阻塞:调用了线程的 sleep() 方法或 join() 方法或发出I/O请求时,线程进入阻塞状态;当sleep()状态超时或 join() 等待线程终止或I/O请求执行完毕时,线程会重新进入就绪状态。
5. 死亡状态(Dead):线程执行完毕或因异常推出了run()方法,该线程生命周期结束。

setPriority:更改优先级
sleep(休眠):休眠多少毫秒;每个对象都有一把锁,sleep不会释放锁
join(加入):插队,当有新的线程加入时,主线程会进入等待状态,一直到调用join()方法的线程执行结束为止。
yield(礼让):线程A抢到CPU了,线程B在外面等待,这是线程A调用yield()方法,就会退出来和线程B一起再抢一次
注:yield()方法只是提出申请释放CPU资源,至于能否成功释放由JVM决定。由于这个特性,一般编程中用不到此方法,但在很多并发工具包中,yield()方法被使用,如AQS、ConcurrentHashMap、FutureTask等。
isAlive:测试线程是否处于活动状态

yield()方法和sleep()方法有什么区别

yield()方法调用后线程处于RUNNABLE(就绪)状态,而sleep()方法调用后线程处于TIME_WAITING(等待)状态,所以yield()方法调用后线程只是暂时的将调度权让给别人,但立刻可以回到竞争线程锁的状态;而sleep()方法调用后线程处于阻塞状态。

sleep()和wait()方法有什么区别:

sleep()睡眠时,保持对象锁,仍然占有该锁;
而wait()睡眠时,释放对象锁。
但是wait()和sleep()都可以通过interrupt()方法打断线程的暂停状态,从而使线程立刻抛出InterruptedException(但不建议使用该方法)。

33. AQS是什么?

AQS就是一个工具类,算是在并发情况下规范了对“资源”或者说“数据”操作的一种机制或者说是规范。专业一点的叫法是:抽象队列同步器


AQS,是AbstractQueuedSynchronizer简称,直翻过来叫抽象的队列式同步器, 也可以称作队列同步器,它是java.util.concurrent.lock包下的一个工具类。直观来讲,AQS是Java提供的一个类,这个类是被Abstract修饰的,需要被子类继承。也就是说AQS是Java并发中用以解决多线程访问共享资源问题的同步机制的基本的框架(或者说是一种规范),为Java并发同步组件提供统一的底层支持。即,AQS是个为各个同步组件提供基本框架的一个抽象类。

AQS这玩意干啥呢?

AQS是给JAVA一系列锁以及同步器或者同步对象的底层提供了实现的框架,你也可以理解成为一种规范。就比如实现像ReentrantLock,CountDownLatch,Semaphore这样的工具。


提供解决同步问题的基础框架。AQS类内维护了一个volatile int型的变量state,用于表示同步状态(锁的释放与获取),同时提供了一些列诸如getstate、setstate、compareAndSetState的方法来管理该同步状态,这些方法是子类中需要重写的部分,并且,AQS提供了模板方法去调用这些重写的方法;另外,AQS用一个虚拟的CLH FIFO的双向队列来管理被阻塞的线程。

AQS概念: 抽象队列同步器,是Java一系列锁以及同步器的底层实现框架

AQS作用: 实现像ReentrantLock,CountDownLatch,Semaphore这样的工具

34. 多线程使用场景

  1. 商品详情
  2. 购物车——声眀式异步
  3. 订单确认页——CompletableFuture

35. CompletableFuture异步编排总结

36. 锁总结

37. 死锁如何产生的?

所谓死锁,是指多个进程在运行过程中因争夺资源而造成的一种僵局;

如果此时有一个线程A,按照先获取锁a、再获得锁b的顺序来获取锁,而此时同时还有一个线程B,按照先获取锁b、再获得锁a的顺序来获得锁。

  1. 互斥条件:一个资源只能被一个线程占有,当这个资源被占有后其他线程就只能等待

  2. 不可剥夺条件:当一个线程不主动释放资源时,此资源一直被拥有线程占有

  3. 请求并持有条件:线程已经拥有了一个资源后,又尝试请求新的资源

  4. 环路等待条件:产生死锁一定是发生了线程资源环形链

38. 如何避免死锁呢?

1、避免使用多个锁

在设计程序时,应尽量避免使用多个锁,尽量只使用一个锁或使用锁的嵌套结构。这样可以降低程序出现死锁的概率。

2、破坏循环等待条件

循环等待是死锁的主要原因之一。为了破坏循环等待条件,可以使用以下几种方法:

  • 按照固定顺序获取锁。例如,如果有两个线程分别需要获取锁A和锁B,那么可以让一个线程按照固定顺序先获取锁A再获取锁B,而另一个线程按照相反的顺序先获取锁B再获取锁A,这样就可以避免死锁的发生。

  • 设置超时时间。如果一个线程在一定时间内无法获取到所需的锁,那么就放弃获取锁,并释放已经持有的锁,以避免死锁的发生。

3、使用可重入锁

可重入锁是一种支持多次加锁的锁,同一个线程可以多次获得同一个锁而不会死锁。Java中的ReentrantLock和synchronized都是可重入锁,使用可重入锁可以避免由于重复获取同一把锁而导致的死锁问题。

4、使用死锁检测工具

Java中提供了一些死锁检测工具,例如jstack、jconsole和VisualVM等,可以帮助开发人员发现和分析死锁问题,及时进行修复。

5、合理设计程序

JVM 

1. 请谈谈你对JVM 的理解?java8 的虚拟机有什么更新?

jvm的结构:类加载器、堆体系(堆:新生代老年代、方法区)、栈、程序计数器、本地方法栈、执行引擎。 

  • 方法区:存储已被虚拟机加载的类元数据信息(元空间)。
  • :存放对象实例,几乎所有的对象实例都在这里分配内存。
  • 虚拟机栈(java栈):虚拟机栈描述的是Java方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧(Stack Frame)用于存储局部变量表、操作栈、动态链接、方法出口等信息。
  • 程序计数器:当前线程所执行的字节码的行号指示器。
  • 本地方法栈:本地方法栈则是为虚拟机使用到的Native方法服务。


java8 的虚拟机:永久代变成了元空间。

2. 什么是OOM ?什么是StackOverflowError?有哪些方法分析?

① OOM: out of memory:堆内存溢出

  • heap space: 堆内存溢出
  • perm space :永久代方法区内存溢出

② SOF:StackOverflowError,栈溢出    
    分析:生成dump文件 通过MAT工具分析
    也可以采用第三方的内存分析工具预防内存泄露等问题:Arthas(阿里开源)

oom:heap space:堆内存溢出

老年代垃圾回收后 仍然存不下新创建的对象
解决:

  1. 扩大堆内存大小
  2. MAT (jvisualvm) 分析dump文件(java项目运行时配置-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=D:\tmp )
  3. 找大对象:判断是不是内存溢出导致的oom,如果是修改代码逻辑及时回收使用过的对象
  4. 以后开发时尽量避免创建大对象:大对象如果超过了伊甸区大小 会直接存入到老年代

堆溢出原因:

(1)Java堆用于存储对象的实例,只要不断地创建对象,并且保证GC roots到对象之间有可达路径来避免垃圾回收机制清除这些对象,那么在对象数量到达最大堆的容量限制后就会产生内存溢出异常

(2)如果虚拟机在拓展栈时无法申请到足够的内存空间,则抛出OutOfMemoryError异常。在虚拟机栈和本地方法栈发生OOM异常场景如下:当Java 程序启动一个新线程时,若没有足够的空间为该线程分配Java栈(一个线程Java栈的大小由-Xss设置决定),JVM将抛出OutOfMemoryError异常。

permgen space:方法区内存溢出

  • 加载的类过多
  • 不需要的类不能加载
  • 扩大方法区大小

栈溢出的原因:

    (1)递归调用

    (2)大量循环或死循环

    (3)全局变量是否过多

    (4)数组、List、Map数据过大

3. JVM 的常用参数调优你知道哪些?

  • XMS XMX : 配置jvm申请的内存大小(分析项目的业务,大概的计算对象的总大小 预估并发时对象占用空间之和 放大10~20倍 ,XMS和XMX一般设置一样大小 新生代大小可以配置的略大,因为99%的对象创建后都会被GC回收)
  • printGCDetails:
参数备注
-Xms初始堆大小。只要启动,就占用的堆大小,默认是内存的1/64
-Xmx最大堆大小。默认是内存的1/4
-Xmn新生区堆大小
-XX:+PrintGCDetails输出详细的GC处理日志

-XX:MetaspaceSize 这个参数是初始化的Metaspace大小

-XX:MaxMetaspceSize 指定元数据区域最大的大小

4. 内存快照抓取和MAT分析DUMP文件知道吗?

  • 配置jvm参数 可以将产生OOM时内存中的数据持久化到DUMP文件中。
  • 通过MAT(jvisualvm.exe)可以解析DUMP文件,分析OOM出现的行数,以及内存中对象列表内容,可以判断内存中哪些对象过大导致OOM。

5. 谈谈JVM中,对类加载器你的认识?类加载器的分类和双亲委派

  • BootStrapClassLoader:启动类加载器  c++实现的,主要负责加载Java核心类库,%JRE_HOME%\lib下的rt.jar、resources.jar、charsets.jar和class等。
  • ExtendsionClassLoader:扩展类加载器    主要负责加载Java核心类库,%JRE_HOME%\lib下的rt.jar、resources.jar、charsets.jar和class等。
  • AppClassLoader: 应用类加载器   加载类路径下的我们自己编写的类。
  • 自定义ClassLoader    可加载指定路径的class文件

默认使用双亲委派机制加载类,防止系统类被我们自定义的类覆盖。

双亲委派:指的是一个类是如何通过类加载器加载到内存中的一个过程

双亲委派机制存在的意义

① 通过委派的方式,可以避免类的重复加载,当父加载器已经加载过某一个类时,子加载器就不会再重新加载这个类。
② 通过双亲委派的方式,还保证了安全性。因为Bootstrap ClassLoader在加载的时候,只会加载JAVA_HOME中的jar包里面的类,如java.lang.Integer,那么这个类是不会被随意替换的,除非有人跑到你的机器上, 破坏你的JDK。那么,就可以避免有人自定义一个有破坏功能的java.lang.Integer被加载。这样可以有效的防止核心Java API被篡改。
双亲委派机制是在classLoader里的loadclass方法里实现的,

双亲委派机制加载类源码流程

  1. 首先判断该类是否已经被加载
  2. 该类未被加载,如果父类不为空,交给父类加载
  3. 如果父类为空,交给bootstrap classloader 加载
  4. 如果类还是无法被加载到,则触发findclass,抛出classNotFoundException(findclass这个方法当前只有一个语句,就是抛出classNotFoundException),如果想自己实现类加载器的话,可以继承classLoader后重写findclass方法,加载对应的类)

6. Stack 栈是什么?

栈也叫栈内存,主管Java程序的运行,是在线程创建时创建,它的生命期是跟随线程的生命期,线程结束栈内存也就释放,对于栈来说不存在垃圾回收问题,只要线程一结束该栈就Over,生命周期和线程一致,是线程私有的。8种基本类型的变量+对象的引用变量+实例方法都是在函数的栈内存中分配。(JDK1.5以后每个线程栈默认大小1M)

7. 栈存储什么?

栈中的数据都是以栈帧(Stack Frame)的格式存在,栈帧是一个内存区块,是一个数据集,是一个有关方法(Method)和运行期数据的数据集。

栈帧中主要保存3 类数据:

  • 本地变量(Local Variables):输入参数和输出参数以及方法内的变量。

  • 栈操作(Operand Stack):记录出栈、入栈的操作。

  • 栈帧数据(Frame Data):包括类文件、方法等等。

8. JVM垃圾判定算法:(对象已死?)

  • 引用计数法(Reference-Counting):不用

  • 可达性分析算法(根搜索算法):如果一个对象没有指向GCRoot的引用链,则该对象是一个垃圾对象

9. GC垃圾回收主要有四大算法:(怎么找到已死对象并清除?)

  • 复制算法(Copying):效率高 占内存

  • 标记清除(Mark-Sweep):先标记后清除 会产生内存碎片

  • 标记压缩(Mark-Compact),又称标记整理:先标记后清除 并会对内存进行整理

  • 分代收集算法(Generational-Collection)

                新生区: 复制算法
                养老区:标记清除或者标记压缩

难道就没有一种最优算法吗?

答案:无,没有最好的算法,只有最合适的算法。==========>分代收集算法

10. 垃圾收集器?

① 串行垃圾回收器
        serial GC    复制算法
        serial Old GC  标记压缩

② 并行垃圾回收器
        parallel GC  复制算法
        parallel Old GC 标记压缩

③ 并发垃圾回收器
        CMS Concurrent Mark Sweep 采用标记清除算法 回收老年代
        parNew    复制算法

整堆收集器: G1:并行与并发,用来替换掉CMS收集器

11. 堆分区介绍?

Heap 堆:一个JVM实例只存在一个堆内存,堆内存的大小是可以调节的。类加载器读取了类文件后,需要把类、方法、常变量放到堆内存中,保存所有引用类型的真实信息,以方便执行器执行,堆内存逻辑上分为三部分

  • Young Generation Space 新生区 Young/New

  • Tenure generation space 养老区 Old/Tenure

  • Permanent Space 永久区 Perm

也称为:新生代(年轻代)、老年代、永久代(持久代)

其中JVM堆分为新生代和老年代

新生区又分为两部分: 伊甸区(Eden space)和幸存者区(Survivor pace) ,所有的对象都是在伊甸区被new出来的。幸存区有两个: S0区(Survivor 0 space)和 S1区(Survivor 1 space)

Jdk1.6及之前: 有永久代,常量池1.6在方法区

Jdk1.7: 有永久代,但已经逐步“去永久代”,常量池1.7在堆

Jdk1.8及之后: 无永久代,常量池1.8在元空间(Metaspace)

分布式事务

1. Seata管理分布式事务的典型生命周期?

TC (Transaction Coordinator) - 事务协调者:维护全局和分支事务的状态,驱动全局事务提交或回滚。

TM (Transaction Manager) - 事务管理器:定义全局事务的范围:开始全局事务、提交或回滚全局事务。

RM (Resource Manager) -资源管理器:管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚,在 Seata 中,分布式事务的执行流程如下: 

  • TM 向 TC 申请开启一个全局事务,全局事务创建成功并生成一个全局唯一的 XID。

  • XID 在微服务调用链路的上下文中传播。

  • RM 向 TC 注册分支事务,将其纳入 XID 对应全局事务的管辖。

  • TM 向 TC 发起针对 XID 的全局提交或回滚决议。

  • TC 调度 XID 下管辖的全部分支事务完成提交或回滚请求。

至此,seata的协议机制总体上看与 XA 是一致的。但是是有差别的:

XA 方案的 RM 实际上是在数据库层,RM 本质上就是数据库自身(通过提供支持 XA 的驱动程序来供应用使用)。

而 Fescar 的 RM 是以二方包的形式作为中间件层部署在应用程序这一侧的,不依赖于数据库本身对协议的支持,当然也不需要数据库支持 XA 协议。这点对于微服务化的架构来说是非常重要的:应用层不需要为本地事务和分布式事务两类不同场景来适配两套不同的数据库驱动。

这个设计,剥离了分布式事务方案对数据库在 协议支持 上的要求。

2. 分布式存储系统的CAP原理(分布式系统的三个指标)?

  1. Consistency(一致性):在分布式系统中的所有数据备份,在同一时刻是否同样的值。

    对于数据分布在不同节点上的数据来说,如果在某个节点更新了数据,那么在其他节点如果都能读取到这个最新的数据,那么就称为强一致,如果有某个节点没有读取到,那就是分布式不一致。

  2. Availability(可用性):在集群中一部分节点故障后,集群整体是否还能响应客户端的读写请求。(要求数据需要备份)

  3. Partition tolerance(分区容忍性):大多数分布式系统都分布在多个子网络。每个子网络就叫做一个区(partition)。分区容错的意思是,区间通信可能失败。

CAP理论就是说在分布式存储系统中,最多只能实现上面的两点。而由于当前的网络硬件肯定会出现延迟丢包等问题,所以分区容忍性是我们无法避免的。所以我们只能在一致性和可用性之间进行权衡,没有系统能同时保证这三点。要么选择CP、要么选择AP。

问题:zookeeper分布式协调组件 是 CP

3. 分布式存储系统的BASE原理?

BASE是对CAP中一致性和可用性权衡的结果,其来源于对大规模互联网系统分布式实践的结论,是基于CAP定理逐步演化而来的,其核心思想是即使无法做到强一致性(Strong consistency),但每个应用都可以根据自身的业务特点,采用适当的方式来使系统达到最终一致性(Eventual consistency)。接下来看看BASE中的三要素:

  1. Basically Available(基本可用)

    基本可用是指分布式系统在出现故障的时候,允许损失部分可用性,即保证核心可用。 电商大促时,为了应对访问量激增,部分用户可能会被引导到降级页面,服务层也可能只提供降级服务。这就是损失部分可用性的体现。

  2. Soft state(软状态)

    软状态是指允许系统存在中间状态,而该中间状态不会影响系统整体可用性。分布式存储中一般一份数据至少会有三个副本,允许不同节点间副本同步的延时就是软状态的体现。mysql replication的异步复制也是一种体现。

  3. Eventually consistent(最终一致性)

    最终一致性是指系统中的所有数据副本经过一定时间后,最终能够达到一致的状态。弱一致性和强一致性相反,最终一致性是弱一致性的一种特殊情况。

BASE模型是传统ACID模型的反面,不同于ACID,BASE强调牺牲高一致性,从而获得可用性,数据允许在一段时间内的不一致,只要保证最终一致就可以了。

4. 分布式锁三种实现方式?

由于jdk没有提供分布式锁的具体实现,所以只能自己手动实现或者使用第三方提供的实现:
① 基于redis/memcache等非关系型数据实现
② 基于MySQL等关系型数据库实现:借助于唯一键索引实现
③ 基于Zookeeper、etcd等实现:借助于znode节点不可重复

  • 性能:redis > zk > mysql
  • 可靠性:zk > redis = mysql 
  • 简易度:redis > MySQL > zk 

5. 基于redis怎么实现分布式锁?

① 独占排他使用:setnx指令

② 防死锁:

  • redis客户端程序获取到锁之后,服务立马宕机,导致死锁。解决死锁:给锁添加过期时间  expire  set k v ex 3 nx
  •  不可重入:可重入

③ 原子性:

  • 加锁和过期时间之间:set k v ex 3 nx
  • 判断和解锁之间:lua脚本

④ 防误删:释放锁之前先判断是否自己的锁,如果是才能删除:lua脚本。

⑤ 可重入:redis的Hash数据模型  +  lua脚本实现的
加锁:

  1. 判断锁是否被占用(exists),如果没有被占用则直接获取锁(hset/hincrby expire)
  2. 判断是否自己占用的锁(hexists),如果是则重入(hincrby expire)
  3. 获取锁失败

解锁:

  1. 判断自己的锁是否存在,如果不存在则返回nil(恶意释放锁)
  2. 直接对重入次数减1(hincrby -1),并判断减1后的值是否为0,如果为0则释放锁(del)
  3. 如果没有减为0,则直接返回0

⑥ 自动续期:Timer定时器(看门狗) + lua脚本实现

(一旦服务器挂掉,续期程序自然无法运行,所以跟过期时间之间不矛盾)
       java.util.Timer 
       redisson使用的是netty中的时间轮实现的
       lua脚本:判断自己的锁是否存在(hexists),存在则重置过期时间(expire)

⑦ 集群问题:
(1)单机部署:单机故障问题
(2)集群部署:主从 哨兵,引发锁失效

  1. 客户端A从master中获取了锁
  2. slaver还没有同步数据,主就宕机了
  3. slaver升级为新的master
  4. 其他客户端B从新的master中获取到锁

     安全失效
(3)使用RedLock红锁算法:有N个相互独立的redis节点(Master),不使用任何主从、哨兵这种协调机制

  1. 客户端程序获取系统当前时间
  2. 客户端程序使用相同的kv从N个节点依次获取锁,从每个节点获取锁式使用一个超时时间(5-50ms),如果超时没有获取到锁则尽快尝试从下一个节点获取锁
  3. 计算获取锁的消耗时间 = 系统当前时间 - step1时间。当且仅当半数以上获取锁 并且 消耗时间小于初始锁定时间时,则认为获取锁成功
  4. 计算剩余锁定时间 = 初始锁定时间 - step3计算的消耗时间
  5. 如果获取锁失败(没有达到半数,或者剩余锁定时间是负数),则解锁所有节点。 

6. 使用redis实现分布式锁?

7. 使用redisson实现分布式锁?

特点:

① 锁名称相同就认为是同一把锁

② 自动续期 默认是30S 每10S 会续期30S

只要锁没有指定释放时间,每隔lockWatchdogTimeout/3 就会给锁续期,续满看门狗时间30S 也就是每10S会进行喂狗操作

private long lockWatchdogTimeout = 30 * 1000;

③ 加锁操作 + 过期时间操作 能保证原子性 获取锁 + 判断锁 + 删除锁 也能保证原子性

④ redisson底层的所有操作都依赖于lua脚本

8. 如何避免分布式锁的死锁问题?

分布式锁的死锁问题可以通过设置超时时间来避免。在获取锁时,可以设置一个超时时间,如果在该时间内没有获取到锁,就放弃获取并返回错误信息。

同时,需要确保加锁和释放锁的过程是原子性的,避免锁没有被正确释放而导致死锁的问题。

8、9 这俩个问题其实就是:redis结合lua脚本,设置过期时间来避免或者解决的! 

9. 如何解决分布式锁的性能问题?

分布式锁的性能问题可以通过一些优化方式来解决,比如减小锁的粒度、使用更高效的锁实现方式、减少锁等待时间等。

另外,也可以考虑将锁的使用尽可能的降到最低限度,以避免过多的锁竞争带来的性能问题。

10. 基于AOP实现分布式锁+缓存?

随着业务中缓存及分布式锁的加入,业务代码变的复杂起来,除了需要考虑业务逻辑本身,还要考虑缓存及分布式锁的问题,增加了程序员的工作量及开发难度。而缓存的玩法套路特别类似于事务,而声明式事务就是用了aop的思想实现的。

缓存+分布式锁 都是对查询操作的性能提升

项目中AOP是如何自定义实现的? (非常重要) 

答:自定义注解+自定义通知的方式实现功能增强
① 自定义注解:
     1. @gmallCache
     2. 该注解中定义
             缓存前缀
             缓存的过期时间
             锁的key值
             防止雪崩随机时间值
② 定义通知方式:目的是为了增强注解
         采用的环绕通知方式:
                1. 定义一个切面类 用@Aspect声明的一个类
                2. 在切面类中定义环绕通知方法

@Aspect
@Component
public class A{
	@Around("切面表达式")  记录是的就是自定义注解的全类路径名
	public Object around(ProceedingJoinPoint jp) throws Throwable(
		// 缓存使用业务
		//分布式锁业务
		//需要手动执行目标方法
		jp.proceed()                            
	)
}

Git

1. 你们那边代码版本工具用的什么?你了解 git reset 和 git revert?

reset用于回退版本,可以遗弃不再使用的提交。

revert在当前提交后面,新增一次提交,抵消掉上一次提交导致的所有变化,不会改变过去的历史,主要是用于安全地取消过去发布的提交。

2. 一次版本提交用到哪几个命令过程?

(1)初始化本地仓库:git init

(2)添加到暂存区:git add 文件名

(3)将暂存区中的代码提交到本地仓库:git commit -m “消息提示” 文件名

(4)查看状态:git status

(5)查看日志:git reflog

(6)查看本地库中的文件:git ls-files

查看状态status、添加到暂存区add、将暂存区提交到本地仓库commit 、再提交到远程仓库push

常用命令:

git rebase -i 交互式界面合并多个分支,让版本提交更简介。乱用就不好了,看不到每次具体版本号,大多公司会禁用。

git branch:查看所有分支

git branch 分支名创建分支
git branch -v查看分支
git checkout 分支名切换分支
git merge 分支名把指定的分支合并到当前分支上

3. git fetch 和 git pull的区别?

git fetch是将远程主机的最新内容拉到本地,用户在检查了以后决定是否合并到工作本机分支中。

而git pull 则是将远程主机的最新内容拉下来后直接合并,即:git pull = git fetch + git merge,这样可能会产生冲突,需要手动解决。

4. commit之后要修改代码怎么办?

方式一:图形化界面:点击IDEA左下方工具栏的git,在上一个版本选择Reset Current Branch to Here(重置当前分支到这里)。这里选择Soft:不修改代码,恢复提交之前状态。Mixed、Hard:代码也回到此时版本了、Keep

方式二:

  1. 执行 git log 查看需要撤销的commit的前面一个提交版本的id;

  2. 执行 git reset --hard commit_id ,该commit_id为需要撤销的commit的提交的前面一个提交的版本,即需要恢复到的提交的id,重置至指定版本的提交,达到撤销提交的目的

  3. 执行 git log 查看,commit提交已撤销

5. 撤销push?

  1. 执行 git log 查看日志,获取需要回退的版本号

  2. 执行 git reset –-soft <版本号> ,重置至指定版本的提交,达到撤销提交的目的

  3. 执行 git push origin 分支名 –-force ,强制提交当前版本号。

6. Idea中cherry pick干嘛的?

从git项目分支提交历史中,选中需要合并过来的提交内容,无需将所有内容进行合并,右键进行 Cherry pick 。有冲突merge合并冲突。

7. git clone 和fork 的区别

  1. git clone 是在将github上的代码克隆到本地

  2. fork 是在github上使用fork将别人的远程仓库复制一份到自己的远程仓库上。

ElasticSearch 

1. 倒排索引:两张表 文档列表 倒排索引区域

所谓倒排索引就是使用关键词去分组,记录文档位置信息,从而提高检索效率。

  • 当我们向es中新增一条文档时,同时会使用ik分词器对索引字段进行分词,把分词后的词条信息及其位置记录到倒排索引区域,
  • 将来我们搜索时,会对条件也进行分词,并到倒排索引区域进行词条的匹配,并锁定文档位置,根据位置信息找到文档返回给用户。

2. 分页问题

  • 一般数据量没有那么大的情况下,使用浅分页即可:from size
  • 如果数据量庞大,此时使用上述方式可能会出现性能问题,可以使用深度分页:scroll
  • 但是现在业务量或者数据量还没有那么庞大,索引我们使用from或者size就可以满足我们的需求

3. 数据扁平化问题

使用嵌套类型 nested

实际开发 

0. 用自己的话描述项目架构!

电商项目一般包括用户注册、商品浏览、购物车、订单管理、支付结算等模块,其架构设计需要考虑以下几个方面:

架构要素具体内容
前端设计电商项目前端设计需要考虑用户体验、交互设计、UI设计等方面,常用的技术包括HTML、CSS、JavaScript、Vue、React等
后端设计电商项目后端设计需要考虑系统架构、数据存储、业务逻辑处理等方面,常用的技术包括Java、Spring、Spring Boot、MyBatis、MySQL等
架构设计电商项目的架构设计需要考虑高可用、高性能、高并发、安全等方面,一般采用分布式架构和微服务架构,包括前端和后端的架构设计
前端架构设计前端架构设计需要考虑前端静态资源的访问速度、请求负载均衡、缓存策略等方面,一般采用CDN技术、反向代理、缓存技术等
后端架构设计后端架构设计需要考虑系统的高可用性、高性能、高并发等方面,一般采用分布式架构和微服务架构。
数据库设计电商项目的数据库设计需要考虑数据的一致性、高可用性、可扩展性等方面,一般采用主从复制、分库分表等技术。常用的数据库包括MySQL、MongoDB、Redis等
安全设计电商项目的安全设计需要考虑用户信息的保密性、系统的可靠性和防止攻击等方面,一般采用SSL/TLS、OAuth、JWT等安全技术
运维设计电商项目的运维设计需要考虑系统的可维护性、监控、报警等方面,一般采用容器化技术、自动化部署、日志分析等技术。常用的工具包括Docker、Kubernetes、ELK等

1. 项目遇到过哪些棘手问题以及如何分析和解决?

关键点在于: 抽象问题具体化

① 常见异常
        OOM
        StackOverflowError
        并发修改异常        

② 应用角度
        redis问题
        rabbitmq问题
        mysql
            mysql cpu使用率100%

③ 代码角度
        复杂业务逻辑if eles  switch case
        stream
        特性使用
        及时发现内存泄漏

④ 分布式角度
        分布式锁
        分布式事务

解决 Java 项目中的棘手问题需要对代码、系统、技术等方面有深入的理解,同时需要结合具体情况进行分析和调试,提高解决问题的能力。

问题分类解决思路
性能问题性能问题是 Java 项目中常见的问题,通常需要进行性能调优和优化。解决方法包括:使用性能分析工具来定位问题,优化代码实现,增加缓存,调整系统参数等
内存泄漏内存泄漏可能导致 Java 应用程序出现异常、崩溃等问题。解决方法包括:使用 JVM 监控工具进行内存分析,寻找引用对象未被释放的原因,修复代码中的内存泄漏问题
分布式系统的一致性问题分布式系统中的一致性问题可能导致数据不一致,解决方法包括:使用分布式锁、分布式事务等技术,确保数据的一致性
安全问题安全问题包括代码漏洞、恶意攻击等,解决方法包括:使用安全框架和加密算法来保护数据和系统安全,及时更新漏洞补丁,进行安全审计和测试
代码质量问题代码质量问题可能导致项目维护成本增加、系统稳定性下降等问题,解决方法包括:使用静态代码分析工具检查代码质量,进行代码重构,使用单元测试等方式提高代码质量
第三方库和组件的问题使用第三方库和组件可能导致兼容性、性能、安全等问题,解决方法包括:使用可靠的第三方库和组件,避免使用不稳定的或有安全漏洞的库和组件,及时更新版本,保持与其他组件的兼容性

2. 你们的项目的代码量大概有多少?你每个月/每周/每天的代码量是多少?

这个问题不是要让你回答一个准确的数字!

项目大概有几十万行、几百万行

3. 服务器数量?

理论上: 模块数*2  mysql集群 redis集群 nginx   60-70台
实际: 需要根据公司的运行情况来进行综合评估 大约10台 

4. 实际在开发的过程中,分配任务是怎么分配的?

按照模块?
还是按照业务?
还是按照功能? (最多):今日修改一点内容或者改bug缺陷,代码量几十行、一两百行

5. 项目组有多少人?

10几个人  5java  2前端   3个测试   2个运维

6. 项目真实并发量 商品数量有多少 表有几张 订单量是多少?

这类问题不需要准备数字,只说大概大

并发量:200-500W
商品数量:10W左右

数据库表:100多张
订单量:1W左右 

同时在线人数:200人

7. 软件开发方式?

① 流程

  • 需求分析:分析需要做什么?
  • 市场调研

② 设计

  • 技术方案和设计文档

③ 实现

  • 前端
  • 后端实现
  • app实现

④ 测试

  • SIT: 系统集成测试
  • 回归测试 (冒烟测试)    
  • 灰度发布
  • UAT: 用户验收测试

        测试验收
        产品验收
        UI验收
        用户验收

⑤ 部署发布(运维)

  • 项目发布: 从windows 到 linux过程

⑥ 分类:

  • 瀑布模型开发方式:现在用的不多
  • 敏捷开发方式(最主流):将之前一个完整的软件开发流程 拆分成多个具有完整软件开发的小流程

8. 平常除了写代码之外,你还做哪些工作?

1.配合.协助测试人员完成测试工作
2.

保证代码质量:
1. 做好回归测试
2. 单元测试 junit

9. 测试是如何将问题描述给开发的?开发又是如何告知测试 bug(缺陷)改好了

① 自己开发一个 
② 禅道
③ jira

10. 技术选型?

将来在简历中编写所用技术的时候,要携带版本号

  • springboot2.3.X
  • springcloud.HR

11. 内存泄漏与内存溢出的区别?

  • 内存泄漏: 原本需要被对象长期引用得不到释放,会导致内存溢出。
  • 内存溢出: 由于养老区内存不够导致溢出

12. Java对象的引用方式?

  • 强引用: 永不回收 eg: User user = new User()
  • 软引用: 内存不够了,才回收
  • 弱引用: 发现即回收

        在我们项目中的购物车模块的校验用户状态之后 使用的ThreadLoacal用来 保存userInfo

        避免内存泄漏

  • 虚引用: 发通知

13. 如何批量将百万数据快速导入数据库?

一个命令:LOAD DATA INFILE

要批量将百万数据快速导入数据库,可以考虑以下几个步骤:

  1. 选择合适的数据库

    选择能够支持快速批量导入的数据库。例如,MySQL和PostgreSQL都提供了快速导入数据的功能

  2. 准备数据

    将数据准备成符合数据库要求的格式,例如使用CSV格式或者数据库支持的其他格式

  3. 使用命令行工具导入数据

    许多数据库都提供了命令行工具来导入数据,这些工具可以快速地导入大量数据。例如,可以使用MySQL的"LOAD DATA INFILE"命令

  4. 使用批量插入API

    许多数据库还提供了批量插入API,这些API可以在代码中使用,可以快速地将大量数据批量插入数据库。例如,MySQL提供了"LOAD DATA LOCAL INFILE"命令和"INSERT INTO ... VALUES"语句

  5. 调整数据库配置

    在导入大量数据时,可能需要调整数据库的配置以提高性能。例如,可以增加数据库缓存大小,调整日志记录级别等

需要注意的是,在导入大量数据时,可能会影响数据库的性能和可用性。因此,在导入数据之前应该备份数据库,并且在导入过程中应该监视数据库的状态,以确保不会出现意外的问题。

14. 系统出问题平时怎么查看的?

查看和解决电商项目中的系统问题需要全面的思考和综合的技能,需要掌握各种工具和技巧,并且需要快速而准确地定位问题所在,并及时采取相应的措施解决问题。

查看维度具体内容
日志电商系统通常会记录各种操作和事件的日志,包括系统日志、应用日志、访问日志等。通过查看日志文件,可以了解系统在什么时间、哪个地方出现了问题,以及问题的具体表现和影响。
监控通过系统监控工具可以实时监测系统的各项指标,包括 CPU 使用率、内存使用率、磁盘使用率、网络带宽等,可以帮助你快速定位问题所在,并对系统进行相应的优化。
代码调试通过在代码中打断点,可以逐步调试程序并查找错误。这需要开发人员熟悉代码,具备一定的调试能力,但可以快速地定位问题所在。
分析数据通过分析数据,可以了解系统中的瓶颈和问题。例如,通过分析订单数据,可以了解哪些商品或地区存在库存紧张或配送不及时的问题,以及用户的购买偏好和行为等
问询用户如果系统出现问题,可以通过咨询用户来了解问题的具体表现和影响,以及用户的反馈和意见。这对于电商项目来说特别重要,因为用户的满意度直接关系到项目的成败

15. Java开发是如何保证代码质量?

方式描述
单元测试单元测试是 Java 开发中保证代码质量的重要手段。通过编写单元测试用例,开发人员可以在开发过程中发现和修复代码问题,保证代码的正确性和可靠性
代码审查代码审查是指开发人员互相检查彼此编写的代码,以发现和纠正代码中的问题。<br/>通过代码审查,可以保证代码符合规范、可读性好、可维护性高等特点,从而提高代码的质量
代码规范Java 开发中使用统一的代码规范,可以提高代码的可读性和可维护性。开发人员应该遵循团队或公司规定的代码规范,并使用代码检查工具进行自动化检查
设计模式设计模式是一种经过验证的解决问题的方法,Java 开发中的设计模式能够帮助开发人员更好地组织代码,提高代码的可扩展性和可重用性
性能优化Java 开发中,性能优化也是保证代码质量的一部分。开发人员可以通过对代码进行分析和优化,提高代码的执行效率和系统的吞吐量,提升用户体验。
自动化构建使用自动化构建工具可以自动化完成代码编译、测试、打包等过程,减少手工操作的错误,提高构建的可靠性和效率,保证软件的质量

16. 如何保证平台安全性?

保证电商平台的安全性需要综合考虑各方面因素,并采用多种技术手段进行保护,保障用户的权益和平台的稳定性

分析角度具体描述
数据安全对于敏感数据,需要采用加密技术进行保护,如HTTPS协议和对称/非对称加密算法等。<br/>此外,对于数据库的读写操作也需要进行权限控制,避免未经授权的操作
用户身份认证用户身份认证是保证电商平台安全性的基础。采用常见的用户认证方式,如基于密码的认证和基于令牌的认证等
订单安全订单安全是电商平台安全的重点之一。需要确保订单的数据完整性和一致性,以避免订单数据被篡改或者丢失
支付安全支付安全是电商平台的重要环节,需要确保用户的支付信息的安全。可以采用第三方支付平台,如支付宝、微信支付等,避免用户支付信息的泄露
网络安全网络安全也是保证电商平台安全的重要方面。需要通过防火墙、流量监控等技术,对网络进行监控和保护,避免未经授权的访问
代码安全编写安全的代码也是保证电商平台安全的重要方面。需要采用最佳实践编写代码,避免代码中出现漏洞,如SQL注入、跨站脚本等
安全审核定期对电商平台进行安全审核,以检测潜在的漏洞和安全问题,并及时修复

17. 当MySQL数据库使用cpu达到100%,会怎么做?

首先去服务器 top命令查看cpu占有率!

① 长时间的大量查询操作导致 CPU 使用率高:可以优化查询语句,例如添加合适的索引、避免使用不必要的 JOIN 操作、减少查询返回的数据量等。

② 网络连接或者 IO 操作导致 CPU 使用率高:可以检查网络连接是否正常、检查磁盘读写是否频繁,或者考虑更换磁盘等硬件设备。

(mysql在8.0版本之后取消了机制:mysql缓存写满了,写入本地磁盘(磁盘读写速度很慢))

③ 其他应用程序或者系统进程占用了大量的 CPU:可以检查其他应用程序或系统进程,如果发现有非必要进程正在占用大量的 CPU,可以考虑停止或者优化这些进程。

④ MySQL 数据库的配置不合理:可以检查 MySQL 的配置参数,例如 innodb_buffer_pool_size、innodb_log_file_size、max_connections 等,适当调整这些参数可以提高 MySQL 的性能。

⑤ MySQL 数据库版本问题:有些 MySQL 的版本可能存在性能问题,可以考虑升级 MySQL 数据库版本,或者使用其他数据库软件代替 MySQL。

18. 项目开发流程是什么?

流程概要流程描述
需求分析和设计在这个阶段,需要明确项目的需求和目标,分析用户需求和系统规划,确定功能和技术选型,制定技术方案和设计文档,进行项目规划和进度安排等
编码和测试在这个阶段,根据需求文档和设计文档,编写代码并进行单元测试、集成测试和系统测试,验证系统的功能和性能,修复缺陷和bug,保证代码的质量和稳定性
集成和部署在这个阶段,将各个模块和组件进行集成测试和系统测试,进行集成部署和联调,确保各个模块和组件之间的协同工作,保证整个系统的稳定性和可靠性
维护和优化在这个阶段,对系统进行性能调优和优化,进行问题排查和修复,定期进行系统巡检和运维,保证系统的稳定性和可用性,同时跟进技术发展和趋势,进行技术升级和改进,以保持系统的竞争力和创新性

19. 电商项目日志是如何实现的?

电商项目的日志系统一般需要记录用户操作日志、系统异常日志、性能日志等,以帮助开发人员快速发现和解决问题。以下是电商项目日志系统的一般设计方案()

log4j漏洞分析及总结_log4j漏洞是什么_csd_ct的博客-CSDN博客

实现步骤具体描述
日志分类根据不同的日志类型进行分类,如业务日志、异常日志、性能日志等。可以通过不同的日志级别进行区分,如DEBUG、INFO、WARN、ERROR、FATAL等级别
日志收集采用中央日志收集的方式,将各个服务节点产生的日志集中到一个日志服务器上,方便管理和查询。可以使用ELK(Elasticsearch、Logstash、Kibana)或者EFK(Elasticsearch、Fluentd、Kibana)等开源工具进行日志收集和管理
日志格式定义统一的日志格式,包括时间、日志级别、线程号、类名、方法名、请求URL、请求参数、返回结果等信息,方便日志查询和分析
日志输出在代码中加入日志输出语句,使用日志框架(如log4j、logback等)进行日志输出。可以根据不同的日志类型输出到不同的日志文件,也可以通过配置将日志输出到控制台、文件、数据库等不同的地方
日志分析通过日志分析工具,如Kibana、Grafana等进行日志分析,可以对业务进行数据统计和分析,发现系统的瓶颈和性能问题,快速解决异常和错误,提高系统的可用性和稳定性

20. 何为多路复用?

每个进程/线程同时处理 多个连接。这也是解决C10k问题主要的思路与方案。

  • I/O多路复用是指利用单个线程来同时监听多个Socket ,并在某个Socket可读、可写时得到通知,从而避免无效的等待,充分利用CPU资源。目前的I/O多路复用都是采用的epoll模式实现,它会在通知用户进程Socket就绪的同时,把已就绪的Socket写入用户空间,不需要挨个遍历Socket来判断是否就绪,提升了性能。
  • 其中Redis的网络模型就是使用I/O多路复用结合事件的处理器来应对多个Socket请求,比如,提供了连接应答处理器、命令回复处理器,命令请求处理器;

21. Redis宕机了怎么处理

第一步:确认 Redis 宕机:首先,需要确认 Redis 真正宕机了,而不仅仅是网络或连接问题。可以尝试使用命令行或客户端工具连接 Redis 实例,或者查看 Redis 服务器的日志文件来获取更多信息。
第二步:恢复 Redis 服务:尝试重启 Redis 服务,可以使用启动脚本或系统服务管理工具来重启 Redis。在一些情况下,Redis 可能仅仅是暂时性的故障,重启可以解决问题。
第三步:恢复数据:如果 Redis 的宕机导致数据丢失或损坏,可以考虑从备份中恢复数据。如果你有 Redis 的备份文件(例如使用 Redis 的持久化功能生成的 RDB 或 AOF 文件),可以使用备份文件来还原数据。

  • 对于 RDB 文件:可以将备份文件拷贝到 Redis 数据目录,然后重启 Redis 服务。Redis 启动时会加载最近的 RDB 文件,并恢复数据。
  • 对于 AOF 文件:可以将备份文件拷贝到 Redis 数据目录,然后在 Redis 的配置文件中将 appendonly 选项设置为 yes,重启 Redis 服务。Redis 启动后会加载 AOF 文件并进行数据恢复。

注意:数据恢复过程可能会导致部分数据丢失,因此请谨慎操作,并确保你的备份文件是最新的。
第四步:高可用和故障转移:如果你的 Redis 系统对于宕机是无法容忍的,可以考虑在架构中引入高可用性和故障转移机制。例如,可以使用 Redis Sentinel、Redis Cluster 或 Redis 主从复制来实现自动故障转移和数据冗余。

  • Redis Sentinel:通过使用 Redis Sentinel,可以监控 Redis 实例的健康状态并自动进行故障转移。
  • Redis Cluster:Redis Cluster 是一个分布式解决方案,将数据分散到多个节点,并提供高可用性和故障转移功能。
  • Redis 主从复制:通过配置 Redis 主从复制,可以将数据复制到多个从节点,从而实现故障转移和数据冗余。

这些机制可以帮助你在 Redis 宕机时实现自动的故障转移和数据恢复,提高系统的可用性。

22. Rabbitmq宕机如何处理

第一步: 检查 RabbitMQ 服务器状态:首先,确认 RabbitMQ 服务器是否真正宕机,可以通过尝试连接 RabbitMQ 服务器或者查看服务器的运行状态来确认。
检查网络连接和配置:确保 RabbitMQ 服务器所在的网络连接正常,并检查 RabbitMQ 配置文件的相关设置,确保没有错误配置导致宕机。

第二步:重启 RabbitMQ 服务器:如果确认 RabbitMQ 服务器宕机,尝试重新启动 RabbitMQ 服务器。可以通过命令行或者管理界面等方式启动 RabbitMQ。
检查日志文件:如果 RabbitMQ 服务器宕机的原因不明确,可以查看 RabbitMQ 的日志文件,通常位于 /var/log/rabbitmq 或 C:\Program Files\RabbitMQ\logs 等位置。日志文件可以提供有关宕机原因和错误信息的线索。

第三步:数据恢复和一致性:如果 RabbitMQ 宕机导致数据丢失或不一致,需要考虑数据的恢复和一致性。可以根据具体情况,从备份中恢复数据,或者重新发布丢失的消息等。
第四步: 高可用性和故障转移:为了应对 RabbitMQ 宕机的情况,可以考虑使用 RabbitMQ 的高可用性特性,如使用集群、镜像队列等来实现故障转移和数据冗余。


预防措施:为了减少 RabbitMQ 宕机的风险,可以采取一些预防措施,如定期备份数据、监控 RabbitMQ 服务器的健康状态、处理异常情况等。

23. 单点登录-注册流程

加盐加密过程 平台不会直接存储用户的明文密码 

24. 单点登录-登录流程

登录并生成token的过程
① 用户提交用户名和密码等登录信息
② 后台获取用户信息并从用户表查询该用户信息

③ 如果不存在,提示用户名或者密码错误后者是用户不存在请去注册等错误信息
④ 如果用户存在,借助于jwt工具类将用户id 用户名 ip 作为有效载荷 生成token 
补充:jwt json web token 

  • 载荷
  • 签名

⑤ 将该token携带到cookie中保存到浏览器
⑥ 登录成功重定向到登录成功的页面

25. 单点登录-鉴权流程

使用的springcloud-gateway 的局部过滤器
① 获取用户的请求路径并判断是否在预先设置的拦截名单中
② 如果不在拦截名单,直接放行
③ 如果在拦截名单,说明要访问的页面需要校验用户身份的页面
④ 分别从请求头或者请求体中获取token

  • 如果token存在于请求头中的话,我们使用reqeust.getHeaders.getFirst("token")
  • 如果token存在于请求体中的话,我们使用reqeust.getCookies[i].getValue()

⑤ 判断token是否为空 说明token不存在或者刚好过期,重定向登录页面重新生成token
⑥ 如果token不为空,使用预先准备好的公钥进行解析token,期间如果解析出现错误,也会重新登录
⑦ 解析token之后,如果IP地址比对成功,放行到后续的登陆之后的业务中
⑧ 否则,意味着IP地址发生了变化,需要重新登录,重新生成新的token 

26. 购物车业务流程

分析需求

  1. 是否支持离线购物车
  2. 购物车中数据该如何存储
  3. 新增 删除 修改购物车
  4. 合并购物车等需求
  5. 查看购物车

技术选型:redis+mysql存储组合

实现数据结构:map(key map(key value))

① 校验用户状态流程

获取登录头信息 userKey,并判断是否为空;

  • 若userKey为空,则制作userKey 后存入cookie并存入UserInfo;
  • 若userKey不为空,解析jwt将UserId存入用户实体类UserInfo;

将UserInfo存入线程的局部变量中,放行。

② 添加商品到购物车

获取登录信息,并判断是否登录;

  • 是登录状态,则购物车key为userId;
  • 未登录状态,则购物车key为userKey;

根据key值从redis中获取用户的购物车 map<skuId,cartJson>;

判断购物车是否包含该商品;

  • 若包含则商品num+1,并入库;
  • 若不包含,则根据skuId查询该商品是否存在,不存在就返回不存在;存在则根据skuId查询销售属性、营销信息、库存信息后加入实时价格缓存,并入库;

③ 查询并合并购物车

获取userKey作为redis中key值查询未登录状态的购物车,并判断是否为空;

  • 若未登录状态购物车为空,获取userId判断是否为空,若为空,返回未登录购物车;不为空,以userId作为key,查询登录的购物车;
  • 若未登录状态购物车不为空,获取未登录状态购物车数据并查询实时价格缓存;

合并登录状态和未登录状态购物车,判断是否包含相同的商品;

  • 包含相同商品,则数量叠加并更新到数据库;
  • 不包含相同商品,则新增购物车记录并入库;

删除未登录购物车数据,返回已经登录购物车列表;

27. 购物车数据同步问题

同步更新redis,异步更新mysql;
springTask  xxl-job 实现分布式定时任务;

商品加入购物车之后,商品的价格可能会被修改,会导致redis中购物车记录的价格和数据库中的价格不一致。需要进行同步,甚至是比价:

解决方案:

  1. 每次查询购物车从数据库查询当前价格(需要远程调用,影响系统并发能力)

  2. 商品修改后发送消息给购物车同步价格(推荐mq消息队列)

pms-service微服务价格修改后,发送消息给购物车,购物车获取消息后,怎么进行价格的同步?

  1. 获取所有人的所有购物车记录,更新对应skuId购物车记录的价格(数据量庞大,效率低下)

  2. redis中单独维护一个商品的价格,数据结构:{skuId: price}

如果使用第二种方案,redis中应该保存两份数据,一份购物车记录数据,一份sku最新价格数据

价格同步的流程如下:

那么查询购物车时,需要从redis中查询最新价格。

  • 6
    点赞
  • 42
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

书启秋枫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值