Java2020面试题技术解剖汇总

2020 Java面试题

一、Spring Boot

SpringBoot自动装配原理

在这里插入图片描述

1. 加载组件到IOC容器

2.3.6.RELEASE

很多配置都可以自己配置文件application.properties中配置

  1. 启动main方法时根据注解@springBootApplication加载了springboot主配置类,开启了自动配置功能@EnableACutoConfigruation

  2. @EnableACutoConfigruation作用:

  • 通过@Import导入选择器AutoConfigurationImportSelector.class从而给IOC容器导入一些组件

    @Import({AutoConfigurationImportSelector.class})
    
  • 导入那些组件取决于该类方法**selectImports()**的内容

  • 根据方法getCandidateConfigurations()下的**SpringFactoriesLoader.loadFactoryNames()**返回的List configurations获取到候选配置

    //该方法中获取的候选的配置
    List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
    
    • 使用SpringFactoriesLoader.loadFactoryNames()扫描所有类路径下的"META-INF/spring.factories"文件,将加载到的spring.factories文件内容封装到Properties对象中

      // 加载spring.factories文件
      List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
       
      //将spring.factories文件封装成Properties对象
      Properties properties = PropertiesLoaderUtils.loadProperties(resource);
      
    • 从properties中获取到EnableAutoConfiguration.class类名对应的值

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.HttpEncodingAutoConfiguration,\

将类路径下META-INF/spring.factories文件里配置的所有的EnableAutoConfiguration的值加入到了容器中

每一个这样的xxxAutoConfiguration类都是容器中的一个组件,都加入容器来做自动配置;

2. 配置类开始自动配置功能
  1. 以**HttpEncodingAutoConfiguration(Http编码自动配置)**为例说明自动配置原理
@Configuration(proxyBeanMethods = false)//表示这是一个配置类,可以给容器添加组件
@EnableConfigurationProperties({ServerProperties.class})//启动ConfigurationProperties功能,将配置文件中对应的值和ServerProperties绑定起来;并把ServerProperties加入到ioc容器中

@ConditionalOnWebApplication(type = Type.SERVLET)//spring底层@COnditional注解,满足指定条件,整个配置类里面的配置才生效; 	判断当前配置类是否为web应用,如果是当前配置类生效

@ConditionalOnClass({CharacterEncodingFilter.class})//判断当前项目有没有指定类CharacterEncodingFilter  spring mvc中进行乱码解决过滤器;

@ConditionalOnProperty(prefix = "server.servlet.encoding",value = {"enabled"}, matchIfMissing = true)//判断配置文件是否存在某个配置 server.servlet.encoding;matchIfMissing意思不存在判断也是成立的 //即使不配置也默认生效
public class HttpEncodingAutoConfiguration {
  
  //他已经和springboot配置文件映射了
  private final Encoding properties;
  
  //只有一个有参构造器情况下,参数值就会从容器中获取
  public HttpEncodingAutoConfiguration(ServerProperties properties) {
        this.properties = properties.getServlet().getEncoding();
    }
  
  @Bean	//给容器中添加一个组件,这个组件的某些值要从properties中获取 
  @ConditionalOnMissingBean
  public CharacterEncodingFilter characterEncodingFilter() {
      CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
      filter.setEncoding(this.properties.getCharset().name());
      filter.setForceRequestEncoding(this.properties.shouldForce (org.springframework. boot.web.servlet.server.Encoding.Type.REQUEST));
      filter.setForceResponseEncoding(this.properties.shouldForce(org.springframework. boot.web.servlet.server.Encoding.Type.RESPONSE));
      return filter;
   }
}

根据当前条件判断,决定这个配置类是否生效。一但配置类生效,这个配置类就会给容器添加组件;这些组件的属性都是从properties类中获取

# 我们能配置的属性都来源与这个功能的properties类
server:
  port: 8088
  servlet:
    encoding:
      charset: UTF-8
      enabled: true
  1. 所有在配置文件中能配置的属性都在xxxProperties类中封装,配置文件可配置什么就参照某个功能对应的这个属性类。
@ConfigurationProperties(prefix = "server",ignoreUnknownFields = true)//从配置文件中获取指定的值和bean属性进行绑定
public class ServerProperties {

总结:

  1. springboot启动会加载大量的自动配置类
  2. 我们需要的功能有没有springboot默认写好的自动配置类
  3. 自动配置类中配置了那些组件,(有我们用的组件,就不需要再来配置)
  4. 给容器中自动配置类添加组件时,会从properties类中获取某些属性。我们可以在配置文件中指定这些属性值

xxxAutoConfiguration:自动配置类

xxxProperties:封装配置文件中相关属性

Spring Boot启动原理

在这里插入图片描述

事件监听组件:

配置在META-INF/spring.factories

ApplicationContextInitializer

SpringApplicationRunListener

只需要放在ioc容器中

ApplicationRuner

CommandLineRunner

启动流程

springboot首先从入口类执行main方法启动,调用run方法

1. 创建SpringApplication对象

SpringApplication只有一个有参构造方法,在有参构造器里初始化了属性值

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
   this.sources = new LinkedHashSet();
  //REACTIVE,SERVLET,NONE判断是什么应用
   this.webApplicationType = WebApplicationType.deduceFromClasspath();
   this.bannerMode = Mode.CONSOLE;
  			...
  //1. 获取类路径下META-INF/spring.factories文件中值为ApplicationContextInitializer的内容保存
   this.setInitializers(this.getSpringFactoriesInstances( ApplicationContextInitializer.class));
  //2. 获取类路径下META-INF/spring.factories文件中值为ApplicationListener监听器的内容保存
   this.setListeners(this.getSpringFactoriesInstances( ApplicationListener.class));
  //3.从多个配置类中判断哪个是有main方法的主配置类
   this.mainApplicationClass = this.deduceMainApplicationClass();
}

2. 执行run方法
public ConfigurableApplicationContext run(String... args) {
    StopWatch stopWatch = new StopWatch();//开始监听
    stopWatch.start();
    ConfigurableApplicationContext context = null;//声明IOC容器
    Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList();//异常报告
    this.configureHeadlessProperty();

    //1. 获取SpringApplicationRunListener,从类路径下META-INF/spring.factories文件中
    SpringApplicationRunListeners listeners = this.getRunListeners(args);
    //2. 回调所有获取SpringApplicationRunListeners的starting()方法
    listeners.starting(); 
    try {
      //封装命令行参数
       ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
      
      //3. 准备环境
      	//获取或者创建完环境 回调SpringApplicationRunListeners.environmentPrepared()方法表示环境准备完成
        ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
      
        this.configureIgnoreBeanInfo(environment);
        Banner printedBanner = this.printBanner(environment);//打印banner
      //4. 创建Applicationcontext 决定创建IOC容器类型REACTIVE、SERVLET
        context = this.createApplicationContext();
      //获取异常报告
        exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context);
      
      //5.准备上下文环境将创建的环境environment保存到IOC中,而且applyInitializers(context)
      	//applyInitializers():回调之前保存的所有ApplicationContextInitializer的initialize
      	//回调所有的SpringApplicationRunListeners.contextPrepared(context)方法	
      	//prepareContext()运行完成后回调所有的SpringApplicationRunListeners.contextLoaded()方法,表示上下文环境加载完成好了
        this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
      
      //6.刷新容器,ioc容器初始化
      	//扫描,创建,加载所有的组件地方(配置类,组件,自动配置)
        this.refreshContext(context);
      // springboot2.3.6空方法
        this.afterRefresh(context, applicationArguments);
        stopWatch.stop();
        if (this.logStartupInfo) {
            (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
        }
 			 //7.回调所有获取SpringApplicationRunListener的started()方法
        listeners.started(context);
       //8.从ioc容器中获取所有的ApplicationRunner和CommandLineRunner进行回调
        this.callRunners(context, applicationArguments);
    } catch (Throwable var10) {
        this.handleRunFailure(context, var10, exceptionReporters, listeners);
        throw new IllegalStateException(var10);
    }
    try {
      //9. 回调所有获取SpringApplicationRunListeners的running()方法
        listeners.running(context);
      //10.整个springBoot应用启动完成以后返回启动的ioc容器;
        return context;
    } catch (Throwable var9) {
        this.handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners)null);
        throw new IllegalStateException(var9);
    }
}
3. SpringApplication执行流程概述
  1. SpringApplication实例化: 执行静态run方法,首先要创建一个SpringApplication对象实例,然后调用这个创建好的SpringApplication的实例方法。在SpringApplication实例初始化的时候,它会提前做几件事情:

在SpringApplication有参构造器中初始化属性的值并

  • 使用SpringFactoriesLoader获取类路径下META-INF/spring.factories文件中值为ApplicationContextInitializer的内容并保存并判断创建ApplicationContext类型是REACTIVE,SERVLET还是普通的。
  • 使用SpringFactoriesLoader获取类路径下META-INF/spring.factories文件中查找并加载所有可用的ApplicationListener并保存。
  • 推断并设置main方法的主配置类。
  1. SpringApplication实例初始化完成并且完成设置后,就开始执行run方法的逻辑了,方法执行首先通过SpringFactoriesLoader.loadFactoryNames获取类路径下META-INF/spring.factories文件中值为SpringApplicationRunListener的内容。回调所有获取SpringApplicationRunListeners的starting()方法;表示springboot应用要执行了。

  2. 创建并配置当前Spring Boot应用将要使用的Environment,创建完环境回调SpringApplicationRunListeners.environmentPrepared()方法表示环境准备完成。(banner)

  3. 创建ConfigurableApplicationContext,决定创建IOC容器类型REACTIVE、SERVLET、普通,BeanUtils反射原理创建

  4. 准备上下文环境prepareContext();将创建的环境environment保存到IOC中,而且applyInitializers(context)
    applyInitializers():回调之前保存的所有ApplicationContextInitializer的initialize
    回调所有的SpringApplicationRunListeners.contextPrepared(context)方法,表示上下文环境准备完成。
    然后回调所有的SpringApplicationRunListeners.contextLoaded()方法,表示上下文应用环境加载完成。

  5. 刷新容器,ioc容器初始化 refreshContext():扫描,创建,加载所有的组件地方(配置类,组件,自动配置)

  6. 回调所有获取SpringApplicationRunListener的started()方法

  7. 从ioc容器中获取所有的ApplicationRunner和CommandLineRunner进行回调

  8. 回调所有获取SpringApplicationRunListeners的running()方法

  9. 整个springBoot应用启动完成以后返回启动的ioc容器;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PoV34IKr-1607756772587)(2020%20Java%E9%9D%A2%E8%AF%95%E9%A2%98.assets/249993-20171212151321051-993064506.jpg)]

@SpringBootApplication = (默认属性)@Configuration + @EnableAutoConfiguration + @ComponentScan。

1. @Configuration

JavaConfig形式的Spring Ioc容器的配置类使用的那个@Configuration,这里的启动类标注了@Configuration之后,本身其实也是一个IoC容器的配置类。

@Configuration的注解类标识这个类可以使用Spring IoC容器作为bean定义的来源。

@Bean注解告诉Spring,一个带有@Bean的注解方法将返回一个对象,该对象应该被注册为在Spring应用程序上下文中的bean。

2. @ComponentScan

@ComponentScan:是自动扫描并加载符合条件的组件(比如@Component和@Repository等)或者bean定义,最终将这些bean定义加载到IoC容器中。

注:所以SpringBoot的启动类最好是放在root package下,因为默认不指定basePackages。

3. @EnableAutoConfiguration

@EnableAutoConfiguration也是借助@Import的帮助,将所有符合自动配置条件的bean定义加载到IoC容器,仅此而已!借助于Spring框架原有的一个工具类:SpringFactoriesLoader的支持,@EnableAutoConfiguration可以智能的自动配置功效才得以大功告成!

自动配置幕后英雄:SpringFactoriesLoader详解

SpringFactoriesLoader属于Spring框架私有的一种扩展方案,其主要功能就是从指定的配置文件META-INF/spring.factories加载配置。

配合@EnableAutoConfiguration使用的话,它更多是提供一种配置查找的功能支持,即根据@EnableAutoConfiguration的完整类名org.springframework.boot.autoconfigure.EnableAutoConfiguration作为查找的Key,获取对应的一组@Configuration类。

@EnableAutoConfiguration自动配置就变成了:从classpath中搜寻所有的META-INF/spring.factories配置文件,并将其中org.springframework.boot.autoconfigure.EnableutoConfiguration对应的配置项通过反射(Java Refletion)实例化为对应的标注了@Configuration的JavaConfig形式的IoC容器配置类,然后加载到IoC容器。

总览:

启动流程主要分为三个部分,第一部分进行SpringApplication的初始化模块,配置一些基本的环境变量、资源、构造器、监听器,第二部分实现了应用具体的启动方案,包括启动流程的监听模块、加载配置环境模块、及核心的创建上下文环境模块,第三部分是自动化配置模块,该模块作为springboot自动配置核心

启动:

每个SpringBoot程序都有一个主入口,也就是main方法,main里面调用SpringApplication.run()启动整个spring-boot程序,该方法所在类需要使用@SpringBootApplication注解,以及@ImportResource注解(if need),@SpringBootApplication包括三个注解,功能如下:

@EnableAutoConfiguration:SpringBoot根据应用所声明的依赖来对Spring框架进行自动配置。

@SpringBootConfiguration(内部为@Configuration):被标注的类等于在spring的XML配置文件中(applicationContext.xml),装配所有bean事务,提供了一个spring的上下文环境。

@ComponentScan:组件扫描,可自动发现和装配Bean,默认扫描SpringApplication的run方法里的Booter.class所在的包路径下文件,所以最好将该启动类放到根包路径下。

SpringBoot启动类

首先进入run方法

run方法中去创建了一个SpringApplication实例,在该构造方法内,我们可以发现其调用了一个初始化的initialize方法

这里主要是为SpringApplication对象赋一些初值。构造函数执行完毕后,我们回到run方法

该方法中实现了如下几个关键步骤:

1.创建了应用的监听器SpringApplicationRunListeners并开始监听

2.加载SpringBoot配置环境(ConfigurableEnvironment),如果是通过web容器发布,会加载StandardEnvironment,其最终也是继承了ConfigurableEnvironment

3.配置环境(Environment)加入到监听器对象中(SpringApplicationRunListeners)

4.创建run方法的返回对象:ConfigurableApplicationContext(应用配置上下文),我们可以看一下创建方法:

方法会先获取显式设置的应用上下文(applicationContextClass),如果不存在,再加载默认的环境配置(通过是否是web environment判断),默认选择AnnotationConfigApplicationContext注解上下文(通过扫描所有注解类来加载bean),最后通过BeanUtils实例化上下文对象,并返回。

5.回到run方法内,prepareContext方法将listeners、environment、applicationArguments、banner等重要组件与上下文对象关联

6.接下来的refreshContext(context)方法(初始化方法如下)将是实现spring-boot-starter-*(mybatis、redis等)自动化配置的关键,包括spring.factories的加载,bean的实例化等核心工作。

配置结束后,Springboot做了一些基本的收尾工作,返回了应用环境上下文。回顾整体流程,Springboot的启动,主要创建了配置环境(environment)、事件监听(listeners)、应用上下文(applicationContext),并基于以上条件,在容器中开始实例化我们需要的Bean,至此,通过SpringBoot启动的程序已经构造完成,接下来我们来探讨自动化配置是如何实现。


自动化配置:

之前的启动结构图中,我们注意到无论是应用初始化还是具体的执行过程,都调用了SpringBoot自动配置模块。
在这里插入图片描述

SpringBoot自动配置模块

该配置模块的主要使用到了SpringFactoriesLoader,即Spring工厂加载器,该对象提供了loadFactoryNames方法,入参为factoryClass和classLoader,即需要传入上图中的工厂类名称和对应的类加载器,方法会根据指定的classLoader,加载该类加器搜索路径下的指定文件,即spring.factories文件,传入的工厂类为接口,而文件中对应的类则是接口的实现类,或最终作为实现类,所以文件中一般为如下图这种一对多的类名集合,获取到这些实现类的类名后,loadFactoryNames方法返回类名集合,方法调用方得到这些集合后,再通过反射获取这些类的类对象、构造方法,最终生成实例。

二、Spring两大核心思想AOP,IOC原理

在这里插入图片描述

spring是轻量级的开源的JavaEE框架

面试台词:

面试官你好,IOC是spring两大核心概念之一,IOC提供了一个IOC的Bean容器,这个容器会帮助我们创建对象,不需要我们手动的创建,IOC有一个强大的功能叫DI依赖注入,我们可以通过写java代码或者xml配置的方式,把我们注入对象所依赖的其他bean自动注入进去,他是通过type类型的方式帮我们注入,正是有个这个DI使得我们IOC有个强大的特点,解耦,比如,jdbctemplate,和sqlsessionfactory这种bean注入容器是需要数据源的,如果我们,jdbctemplate和数据源强耦合在一起,会导致一个问题,我们使用jdbcTemplent必须使用他的数据源,依赖注入可以让我们在注入jdbcTemplent时只需要依赖一个DataSource接口,不依赖具体的实现,这样的实现好处是,将来我们给容器注入一个的数据源,他会自动注入给jdbctemplate,其他的同理,这样jdbcTemplent就和我们的数据源达到解耦和,spring在启动初始化时就会把所有的bean创建好,这样的话,程序在运行的时候,不需要在创建bean,这样运行速度会更快,ioc容器管理着很多bean,有单例的也可能有多例的,用的时候直接注入就行,这是我理解的ioc。

AOP是面向切面编程,它同样非常重要,在日常工作中会用到很多重复的代码,比如事务处理,日志,需要在很多类里同时写入, 把共有的代码使抽取出来,用aop切入到指定位置,这样极大的减少了代码冗余,和编码时间,这aop实现是依靠动态代理实现的,如果将来我们将来要代理的这个对象,他有接口,我们就使用java原生的动态代理来完成动态代理的创建,如果没有实现接口就会采用cglib技术进行动态代理类的创建。在项目中使用不多,但无处不在,ioc和aop的思想非常重要。

1. IOC(Inverse Of Control): 控制反转

IOC(概念和原理)
  1. 什么是IOC

    1. 控制反转,把对象的创建和对象之间的调用过程,交给spring进行管理
    2. 使用IOC目的:为了耦合度降低
  2. IOC底层原理

    xml解析、工厂模式、反射(只需要修改xml配置文件 )

    第一步:配置xml文件,配置创建的对象

    <bean id="dao" class="com.UserDao"></bean>
    

    第二步:有service类和Dao类,创建工厂类

    class UserFactory{
      public static UserDao getDao(){
        String classValue=class属性值;//1. xml解析得到类的全路径名
        Class clazz = Class.forName(classValue);//2. 通过反射创建对象
        return (UserDao)clazz.newInstance();
      }
    }
    
  3. IOC接口

    1. IOC思想基于IOC容器完成,IOC容器底层就是对象工厂

    2. Spring提供了IOC容器实现的两种方式:(两个接口)加载配置文件,创建配置的对象

      1. BeanFactory:IOC容器基本的实现,是Spring内部的实用类,不提供给开发

        特点:加载配置文件时候不创建对象,只有在获取对象才去创建对象

      2. ApplicationContext:BeanFactory接口的子接口,提供了更强大的功能,一般有开发使用。

        特点:加载配置文件时候就会把配置文件进行对象创建

      3. ApplicationContext接口有实现类

        FileSystemXmlApplicationContext:路径是:盘符路径

        ClassPathXmlApplicationContext:路径是:当前项目下的路径

  4. IOC操作Bean管理

    1. 什么是bean管理:指的是两个操作

      spring创建对象

      spring注入对象

    2. Bean管理操作两种方式

      1. 基于xml配置文件方式实现
      2. 基于注解方式实现
IOC操作Bean管理(基于xml方式)
  1. 基于xml方式创建对象

    在spring配置文件中,使用bean标签,里面添加对应的属性,就可以完成对象创建

    <bean id="dao" class="com.UserDao"></bean>
    

    在bean标签里有很多属性,介绍常用属性

    1. id属性:获取对象的唯一标识
    2. class属性:创建对象类所在的类全路径(包.类名)

    创建对象时,默认执行无参构造方法完成对象创建

  2. 基于xml方式注入属性

    DI:依赖注入,就是注入属性—>DI是IOC的一个具体实现,需要在创建对象的基础上实现

    第一种:set注入方式

    第二种:有参构造注入

    第三种:p名称空间注入:使用p名称空间注入,简化xml配置

    第四种:自动注入(autowire)

    <!-- 在xml头部插入-->
    <xml xmlns:p="http://www.springframework.org/schema/p"></xml>
     <bean id="dao" class="com.UserDao" P:bname="李白"></bean>
    
  3. xml注入其他类型属性

    注入属性-字面量:

    ​ null值:

    <property name="bname"> <null/> </property>
    

    ​ 属性包含特殊符号:<> 用 转义 < >

    <property name="bname"> <value><![CDATA[ <<北京>> ]]]> </value></property>
    

    注入属性-外部bean

    <bean id="userService" class="com.UserService">
      <!--注入userDao对象 name:类属性名 ref:userDao对象bean标签id值-->
    	<property name="userDao" ref="userDao1"></property>
    </bean>
    <bean id="userDao1" class="com.UserDaoImpl"></bean>
    

    注入属性-内部bean和级联赋值

    <bean id="emp" class="com.Emp">
    	<property name="ename" value="张三"></property>
      <property name="dept">
        <bean id="dept" class="com.Dept">
          <property name="dept" value="财务部门"></property>
        </bean>
      </property>
    </bean>
    
  4. 工厂bean:在配置文件中定义bean类型可以和返回类型不一样,实现接口FactoryBean

  5. bean作用域:创建bean是单例还是多例,scope=singleton或prototype

    单实例:加载spring配置文件时就会创建单实例对象

    多实例:不是在加载spring配置文件创建对象,在执行getBean()方法时创建对象。

  6. bean生命周期:从对象创建到对象销毁

    1. 通过构造器创建bean
    2. 为bean属性设置值(调用set方法)
    3. 把bean实例传递bean后置处理器的方法postProcessBeforeInitialization
    4. 调用bean的初始化方法(需要配置初始化方法)
    5. 把bean实例传递bean后置处理器的方法postProcessAfterInitialization
    6. bean可以使用,bean获取到
    7. 当容器关闭时,调用bean的销毁方法(需要配置销毁方法)
  7. bean自动装配

    bean标签属性autowire,配置自动装配

    byName:根据属性名称注入,注入值bean的id和属性名一样

    byType:分拣员属性类型注入,

    <bean id="userDao1" class="com.UserDaoImpl" autowire="byName,byType"></bean>
    
  8. 引入外部属性文件

<context:property-placeeholder location="classpath:jabc.properties"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
	<property name="driverClassName" value="${prop.driverClass}"></property>
</bean>
IOC操作Bean管理(基于注解方式)
  1. 注解:注解是代码特殊标记,格式:@注解名称(属性名=属性值,属性名=属性值…)

    注解作用在方法,属性,类上

    使用目的:简化xml配置

  2. Spring正对Bean管理创建对象提供注解

    @Component :

    @Service :业务逻辑层

    @Controller :web控制层

    @Repository :dao持久层

    功能一样,都可以用来创建bean实例

  3. 基于注解方式创建对象

    1. 引入aop依赖

    2. 开启组件扫描

      <contex:component-scan base-package="com.a,com.b"></contex:component-scan>
      
    3. 创建类上添加注解

      @Component(value=“userService”):在注解里value可以省略,默认类名称,首字母小写

  4. 开启组件扫描细节配置

  5. 基于注解实现属性注入

    ​ 针对对象类型非普通类型

    1. @Autowired :根据属性类型自动注入
    2. @Qualifier :根据属性名称自动注入,和autowired一起使用,用在多实现类
    3. @Resource :可以根据类型(默认)注入,也可以根据名称注入,name=“指定名称”,javax包下的
    4. @Value :注入普通类型属性
  6. 完全注解开发

    创建配置类用@Configuration://作为配置类,替代xml配置文件

    @ComPonentScan(basePackage={“com.p”}) :开启注解扫描

    //加载配置类
    ApplicationContext context=new AnnotationConfigApplicationContext(SpringConfig.class);
    

2. AOP(Aspect-Oriented Programming)面向切面编程

  1. 什么是aop:面向切面编程,历用aop可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发效率。

    通俗描述:不通过修改源代码方式在主干功能里添加新功能

    例如,登录功能新增权限控制模块。

  2. AOP底层原理

    底层使用动态代理,有两种情况的动态代理

    1. 有接口情况:使用JDK动态代理:创建接口实现类代理对象,增强类的方法。
    2. 没有接口情况:使用cglib动态代理 :创建子类的代理对象,增强类的方法。
  3. JDK动态代理:使用Proxy类里的方法创建代理对象

    调用newProxyInstans方法

    方法三个参数:

    ClassLoader loader:类加载器

    Class<?>[] interfaces:增强方法所在的类,这个类实现的接口,支持多个接口

    InvocationHandler:实现这个接口InvocationHandler,创建代理对象,写增强的部分

  4. 编写JDK动态代理代码步骤

    1. 创建接口,定义方法
    2. 创建接口实现类,实现方法
    3. 创建接口实现类的代理对象
    用Proxy.newProxyInstance()创建接口实现类的代理对象
    1.创建类实现InvocationHandler接口
    2.传入被代理的对象
    3.在重写的invoke方法种写增强实现
    4.使用method.invoke()执行被增强的方法
    
    public class MyJdkProxy {
        public static void main(String[] args) {
            //创建接口实现类代理对象
            Class[] interfaces={UserDao.class};
            UserDao userDao = new UserDaoImpl();
            UserDao ud= (UserDao)Proxy.newProxyInstance(MyJdkProxy.class.getClassLoader(), interfaces, new UserDaoProxy(userDao));
            ud.add(2,5);
        }
    }
    //创建代理对象
    class UserDaoProxy implements InvocationHandler{
        //1. 创建的是谁的代理对象,把谁传进来
        //用有参构造器传递
        private Object obj;
        public  UserDaoProxy(Object obj){this.obj=obj;}
        //增强的逻辑d
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            //2. 在invoke方法里书写增强的逻辑
            System.out.println("方法之前执行======"+method.getName()+ Arrays.toString(args));// 方法之前
            Object res = method.invoke(obj, args);// 被增强的方法执行
            System.out.println("方法之后执行========"); // 方法之后
            return res;
        }
    }
    
  5. AOP术语

    1. 连接点:可以被增强的方法就是连接点

    2. 切入点:实际被增强的方法叫切入点

    3. 通知(增强):实际增强的逻辑部分称为通知

      前置通知(MethodBeforeAdvice)

      后置通知(AfterReturningAdvice)

      环绕通知(MethodInterceptor )

      异常通知(ThrowsAdvice)

      最终通知 finally

    4. 切面:(是动作)把通知应用到切入点的过程叫做切面

    Spring框架一般都是基于AspectJ实现AOP操作

aop常用注解:

@before @after @AfterReturning @AfterThrowing @Around

spring4.xx:打印顺序:Around前–>before–>业务–>环绕后–>after–>afteReturnning

​ Around前–>before–>after–>afterThrowing

spring5.xx:打印顺序:Around前–>before–>业务–>afteReturnning–>after–>Around环绕后

​ Around前–>before–>afterThrowing–>after

开发流程:

1.编写原始类

2.编写额外功能(通知 advice)

3.定义切点 :定义额外功能加入得位置。 比如: 在哪个包中的 哪个类中 哪个方法上增加额外功能。

4.编织 根据切入点定义的位置 将额外功能加入到原始类。

(切面=额外功能+切点)

经典应用:事务管理、性能监视、安全检查、缓存 、日志等

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans 
                           http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/aop 
                           http://www.springframework.org/schema/aop/spring-aop.xsd">
    <!-- 1 创建目标类 -->
    <bean id="userServiceId" class="com.itheima.c_spring_aop.UserServiceImpl"></bean>
    <!-- 2 创建切面类(通知) -->
    <bean id="myAspectId" class="com.itheima.c_spring_aop.MyAspect"></bean>
    <!-- 3 aop编程 
        3.1 导入命名空间
        3.2 使用 <aop:config>进行配置
                proxy-target-class="true" 声明时使用cglib代理
            <aop:pointcut> 切入点 ,从目标对象获得具体方法
            <aop:advisor> 特殊的切面,只有一个通知 和 一个切入点
                advice-ref 通知引用
                pointcut-ref 切入点引用
        3.3 切入点表达式
            execution(* com.itheima.c_spring_aop.*.*(..))
            选择方法 返回值任意   包         类名任意 方法名任意 参数任意
 
    -->
    <aop:config proxy-target-class="true">
        <aop:pointcut expression="execution(* com.itheima.c_spring_aop.*.*(..))" id="myPointCut"/>
        <aop:advisor advice-ref="myAspectId" pointcut-ref="myPointCut"/>
    </aop:config>
</beans>

静态代理: 针对每个具体类分别编写代理类, 针对一个接口编写一个代理类;

动态代理: 针对一个方面编写一个InvocationHandler,然后借用JDK反射包中的Proxy类为各种接口动态生成相应的代理类

三、Spring MVC流程

1、 用户发送请求至前端控制器DispatcherServlet。
2、 DispatcherServlet收到请求调用HandlerMapping处理器映射器。
3、 处理器映射器找到具体的处理器(可以根据xml配置、注解进行查找),生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet。
4、 DispatcherServlet调用HandlerAdapter处理器适配器。
5、 HandlerAdapter经过适配调用具体的处理器(Controller,也叫后端控制器,我们使用了@Controller注解,添加了@Controller注解的类就可以担任控制器(Action)的职责,)。
6、 Controller执行完成返回ModelAndView。
7、 HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet。
8、 DispatcherServlet将ModelAndView传给ViewReslover视图解析器。
9、 ViewReslover解析后返回具体View。
10、DispatcherServlet根据View进行渲染视图(即将模型数据填充至视图中)。
11、 DispatcherServlet响应用户。

如下图:
在这里插入图片描述

组件说明:
以下组件通常使用框架提供实现:

  • DispatcherServlet:作为前端控制器,整个流程控制的中心,控制其它组件执行,统一调度,降低组件之间的耦合性,提高每个组件的扩展性。
  • HandlerMapping:通过扩展处理器映射器实现不同的映射方式,例如:配置文件方式,实现接口方式,注解方式等。
  • HandlAdapter:通过扩展处理器适配器,支持更多类型的处理器。
  • ViewResolver:通过扩展视图解析器,支持更多类型的视图解析,例如:jsp、freemarker、pdf、excel等。

组件:
1、前端控制器DispatcherServlet,由框架提供
作用:接收请求,响应结果,相当于转发器,中央处理器。有了dispatcherServlet减少了其它组件之间的耦合度。
用户请求到达前端控制器,它就相当于mvc模式中的c,dispatcherServlet是整个流程控制的中心,由它调用其它组件处理用户的请求,dispatcherServlet的存在降低了组件之间的耦合性。
2、处理器映射器HandlerMapping,由框架提供
作用:根据请求的url查找Handler
HandlerMapping负责根据用户请求找到Handler即处理器,springmvc提供了不同的映射器实现不同的映射方式,例如:配置文件方式,实现接口方式,注解方式等。
3、处理器适配器HandlerAdapter
作用:按照特定规则(HandlerAdapter要求的规则)去执行Handler
通过HandlerAdapter对处理器进行执行,这是适配器模式的应用,通过扩展适配器可以对更多类型的处理器进行执行。
4、处理器Handler( 工程师开发)
注意:编写Handler时按照HandlerAdapter的要求去做,这样适配器才可以去正确执行Handler
Handler 是继DispatcherServlet前端控制器的后端控制器,在DispatcherServlet的控制下Handler对具体的用户请求进行处理。
由于Handler涉及到具体的用户业务请求,所以一般情况需要工程师根据业务需求开发Handler。
5、视图解析器View resolver由框架提供
作用:进行视图解析,根据逻辑视图名解析成真正的视图(view)
View Resolver负责将处理结果生成View视图,View Resolver首先根据逻辑视图名解析成物理视图名即具体的页面地址,再生成View视图对象,最后对View进行渲染将处理结果通过页面展示给用户。 springmvc框架提供了很多的View视图类型,包括:jstlView、freemarkerView、pdfView等。

一般情况下需要通过页面标签或页面模版技术将模型数据通过页面展示给用户,需要由工程师根据业务需求开发具体的页面。

6、视图View(需要工程师开发jsp…)

View是一个接口,实现类支持不同的View类型(jsp、freemarker、pdf…)
核心架构的具体流程步骤如下:
1、首先用户发送请求——>DispatcherServlet,前端控制器收到请求后自己不进行处理,而是委托给其他的解析器进行处理,作为统一访问点,进行全局的流程控制;

2、DispatcherServlet——>HandlerMapping, HandlerMapping 将会把请求映射为HandlerExecutionChain 对象(包含一个Handler 处理器(页面控制器)对象、多个HandlerInterceptor 拦截器)对象,通过这种策略模式,很容易添加新的映射策略;

3、DispatcherServlet——>HandlerAdapter,HandlerAdapter 将会把处理器包装为适配器,从而支持多种类型的处理器,即适配器设计模式的应用,从而很容易支持很多类型的处理器;

4、HandlerAdapter——>处理器功能处理方法的调用,HandlerAdapter 将会根据适配的结果调用真正的处理器的功能处理方法,完成功能处理;并返回一个ModelAndView 对象(包含模型数据、逻辑视图名);

5、ModelAndView的逻辑视图名——> ViewResolver, ViewResolver 将把逻辑视图名解析为具体的View,通过这种策略模式,很容易更换其他视图技术;

6、View——>渲染,View会根据传进来的Model模型数据进行渲染,此处的Model实际是一个Map数据结构,因此很容易支持其他视图技术;

7、返回控制权给DispatcherServlet,由DispatcherServlet返回响应给用户,到此一个流程结束。

下边两个组件通常情况下需要开发:
Handler:处理器,即后端控制器用controller表示。

View:视图,即展示给用户的界面,视图中通常需要标签语言展示模型数据。

四、数据库优化

面试回答数据库优化问题从以下几个层面入手

(1)根据服务层面:配置mysql性能优化参数;

(2)从系统层面增强mysql的性能:优化数据表结构、字段类型、字段索引、分表,分库、读写分离等等。

(3)从数据库层面增强性能:优化SQL语句,合理使用字段索引。

(4)从代码层面增强性能:使用缓存和NoSQL数据库方式存储,如MongoDB/Memcached/Redis来缓解高并发下数据库查询的压力。

(5)减少数据库操作次数,尽量使用数据库访问驱动的批处理方法。

(6)不常使用的数据迁移备份,避免每次都在海量数据中去检索。

(7)提升数据库服务器硬件配置,或者搭建数据库集群。

(8)编程手段防止SQL注入:使用JDBC PreparedStatement按位插入或查询;正则表达式过滤(非法字符串过滤);

1. SQL优化方式

1、在表中建立索引,优先考虑wheregroup by使用到的字段。

2、尽量避免使用select *,返回无用的字段会降低查询效率。
优化方式:使用具体的字段代替*,只返回使用到的字段

3、尽量避免使用innot in,会导致数据库引擎放弃索引进行全表扫描。
优化方式:如果是连续数值,可以用between代替。如下:
SELECT * FROM t WHERE id BETWEEN 2 AND 3
如果是子查询,可以用exists代替。如下:
SELECT * FROM t1 WHERE EXISTS (SELECT * FROM t2 WHERE t1.username = t2.username)

4、尽量避免使用or,会导致数据库引擎放弃索引进行全表扫描。如下:
SELECT * FROM t WHERE id = 1 OR id = 3
优化方式:可以用union代替or。如下:
SELECT * FROM t WHERE id = 1
UNION
SELECT * FROM t WHERE id = 3
(PS:如果or两边的字段是同一个,如例子中这样。貌似两种方式效率差不多,即使union扫描的是索引,or扫描的是全表)

5、尽量避免在字段开头模糊查询,会导致数据库引擎放弃索引进行全表扫描。如下:
SELECT * FROM t WHERE username LIKE '%li%'
优化方式:尽量在字段后面使用模糊查询。如下:
SELECT * FROM t WHERE username LIKE 'li%'

6、尽量避免进行null值的判断,会导致数据库引擎放弃索引进行全表扫描。如下:
SELECT * FROM t WHERE score IS NULL
优化方式:可以给字段添加默认值0,对0值进行判断。如下:
SELECT * FROM t WHERE score = 0

7、尽量避免在where条件中等号的左侧进行表达式、函数操作,会导致数据库引擎放弃索引进行全表扫描。如下:
SELECT * FROM t2 WHERE score/10 = 9
SELECT * FROM t2 WHERE SUBSTR(username,1,2) = 'li'
优化方式:可以将表达式、函数操作移动到等号右侧。如下:
SELECT * FROM t2 WHERE score = 10*9
SELECT * FROM t2 WHERE username LIKE 'li%'

8、当数据量大时,避免使用where 1=1的条件。通常为了方便拼装查询条件,我们会默认使用该条件,数据库引擎会放弃索引进行全表扫描。如下:
SELECT * FROM t WHERE 1=1
优化方式:用代码拼装sql时进行判断,没wherewhere,有whereand9.程序要尽量避免大事务操作,提高系统并发能力。
10.一个表的索引数最好不要超过6

2. 索引面试题

创建索引得时机:

  1. 表经常进行 SELECT 操作
  2. 表很大(记录超多),记录内容分布范围很广
  3. 列名经常在 WHERE 子句或连接条件中出现

索引优缺点:

  • 索引加快数据库的检索速度
  • 索引降低了插入、删除、修改等维护任务的速度(虽然索引可以提高查询速度,但是它们也会导致数据库系统更新数据的性能下降,因为大部分数据更新需要同时更新索引)
  • 唯一索引可以确保每一行数据的唯一性,通过使用索引,可以在查询的过程中使用优化隐藏器,提高系统的性能
  • 索引需要占物理和数据空间

mysql性能优化

慢sql查询优化手段:

合理设计索引 b+树,hash

什么是索引:索引是帮助数据库高效获取数据得排好序得数据结构。

索引数据结构:

二叉树

红黑树

hash表

B-Tree

1.叶节点具有相同的深度,叶节点的指针为空
2.所有索引元素不重复
3.节点中的数据索引从左到右递增排列
  1. B+Tree:
1.非叶子节点不存储data,只存储索引(冗余),可以放更多得索引
2.叶子节点包含所有索引字段
3.叶子节点用指针连接,提高区间访问性能
  1. Hash表
1.对索引的key进行一次hash计算就可以定位出数据的存储位置
2.很多时候hash索引要比B+树索引更高效
3.仅能满足= in ,不支持范围查询
4.hash冲突问题

存储引擎MyISAM索引实现

索引文件和数据文件是分离得
MYI:存储主键索引(b+树)
MYD:存储数据
sql查询执行过程:如果是索引字段,快速定位到索引位置,myisam data元素存储得是索引所在行得磁盘文件地址,然后根据地址在MYD文件定位数据 

存储引擎InnoDB索引实现(聚集)

索引和数据都存储在idb文件 
1.表数据结构本身就按B+Tree组织的一个索引结构文件
2.聚集索引一叶节点包含了完整的数据记录
3.为什么建议InnoDB表必须建主键,并且推荐使用整型的自增主键?
答:用B+树来组织所有行数据,主键默认索引来来维护数据,如果不建主键,mysql会找一列不相等的数据来维护到B+树里,如果没有这样的唯一一列数据,mysql会在后台自动创建一列隐藏数据来维护整张表数据。会浪费mysql资源。
 不建议用uuid做主机,既不是整型,又不自增,整型比对效率高,字符串复杂,整型占用空间小
4.为什么非主键索引结构叶子节点存储的是主键值?(一致性的节省存储空间)

五、单体架构事务控制

1、事务是什么?

一个事务是由一到多条SQL命令组成, 组成事务的一组SQL命令是不可分割的整体,要么一起成功要么一起失败。 为了保证数据的完整性和一致性需要做事务的控制。

2、不同框架对事物的管理机制

JDBC中的事务管理机制:JDBC中默认事务是自动提交的,由connection对象管理。

mybatis中事务管理机制: 默认事务是自动回滚的。

Spring框架的事务管理

Spring框架管理事务的对象: DataSourceTransactionManager

Spring框架管理事务的机制: Spring采用的是声明式事务管理 。

PROPAGATION_REQUIRED(propagation_required)【默认】 : 事务是必须的。外层有事务, 内层加入外层的事务中。外层没有事务,内层自己创建一个新的事务。

PROPAGATION_SUPPORTS(propagation_supports): 支持事务。外层有事务,内层加入外层的事务。外层没有事务,不会主动创建事务。

PROPAGATION_REQUIRES_NEW(propagation_requires_new): 无论什么情况,都会创建新的事务。外层有事务,先挂起外层的事务,自己创建一个新的事务,执行完毕,再去执行外层的事务。外层没有事务,自己主动创建一个新的事务。

PROPAGATION_NEVER(propagation_never): 从来都不能运行在有事务的环境,如果有事务则抛出异常。

PROPAGATION_MANDATORY(propagation_mandatory):必须运行在事务环境, 没有事务则会抛出异常。

1. 本地事务

什么是本地事务?

在计算机系统中,更多的是通过对关系型数据库来控制事务,这是利用数据库本身的事务特性来实现的,因此叫做数据库事务,而数据库通常和应用在同一个服务器,所以基于关系型数据库的事务又被称为本地事务。

数据库事务四大特性(ACID)

原子性(Atomicity)

原子性是指一个事物内所有操作共同组成一个原子包,要么全部成功,要么全部失败回滚。也就是说事务的操作如果成功就必须要完全应用到数据库,如果操作失败则不能对数据库有任何影响。

一致性(Consistency)

数据库事物的一致性就规定了事物提交前后,永远只可能存在事物提交前的状态和事物提交后的状态,从一个一致性的状态到另一个一致性状态,而不可能出现中间的过程态

隔离性(Isolation)

隔离性是当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离,一个事务不能看到其他事务运行过程中的中间状态。通过配置事务隔离级别可以避免脏读,重复读等问题。

持久性(Durability)

持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作。

​ 数据库管理系统采用日志来保证事务的原子性、一致性和持久性

​ 数据库管理系统采用锁机制来实现事务的隔离性

事务得隔离级别

没有事务 并发情况下出现得问题有:

  1. 脏读

脏读是指在一个事务处理过程里读取了另一个未提交的事务中的数据。

当一个事务正在多次修改某个数据,而在这个事务中这多次的修改都还未提交,这时一个并发的事务来访问该数据,就会造成两个事务得到的数据不一致。例如:用户A向用户B转账100元,对应SQL命令如下

update account set money=money+100 where name=’B’;  --(此时A通知B)
update account set money=money - 100 where name=’A’;

当只执行第一条SQL时,A通知B查看账户,B发现确实钱已到账(此时即发生了脏读),而之后无论第二条SQL是否执行,只要该事务不提交,则所有操作都将回滚,那么当B以后再次查看账户时就会发现钱其实并没有转。

  1. 不可重复读(*update)

不可重复读是指在对于数据库中的某个数据,一个事务范围内多次查询却返回了不同的数据值,这是由于在查询间隔,被另一个事务修改并提交了。

例如事务T1在读取某一数据,而事务T2立马修改了这个数据并且提交事务给数据库,事务T1再次读取该数据就得到了不同的结果,发生了不可重复读。

不可重复读和脏读的区别:脏读是某一事务读取了另一个事务未提交的脏数据,而不可重复读则是读取了前一事务提交的数据。

  1. 幻读(虚读)(*insert)

    幻读是事务非独立执行时发生的一种现象。例如事务T1对一个表中所有的行的某个数据项做了从“1”修改为“2”的操作,这时事务T2又对这个表中插入了一行数据项,而这个数据项的数值还是为“1”并且提交给数据库。而操作事务T1的用户如果再查看刚刚修改的数据,会发现还有一行没有修改,其实这行是从事务T2中添加的,就好像产生幻觉一样,这就是发生了幻读。

    幻读和不可重复读都是读取了另一条已经提交的事务(这点就脏读不同),所不同的是不可重复读查询的都是同一个数据项,而幻读针对的是一批数据整体(比如数据的个数)。

    避免不可重复读需要锁行就行

    避免幻影读则需要锁表

数据库隔离级别:

① Serializable (串行化):可避免脏读、不可重复读、幻读的发生。

② Repeatable read (可重复读):可避免脏读、不可重复读的发生。

③ Read committed (读已提交):可避免脏读的发生。

④ Read uncommitted (读未提交):最低级别,任何情况都无法保证。

以上四种隔离级别最高的是Serializable级别,最低的是Read uncommitted级别,当然级别越高,执行效率就越低。像Serializable这样的级别,就是以锁表的方式(类似于Java多线程中的锁)使得其他的线程只能在锁外等待,所以平时选用何种隔离级别应该根据实际情况。在MySQL数据库中默认的隔离级别为Repeatable read (可重复读)。

在MySQL数据库中,支持上面四种隔离级别,默认的为Repeatable read (可重复读);而在Oracle数据库中,只支持Serializable (串行化)级别和Read committed (读已提交)这两种级别,其中默认的为Read committed级别。

在MySQL数据库中查看当前事务的隔离级别:

select @@tx_isolation;

在MySQL数据库中设置事务的隔离 级别:

    set  [glogal | session]  transaction isolation level 隔离级别名称;

    set tx_isolation=’隔离级别名称;

记住:设置数据库的隔离级别一定要是在开启事务之前!

如果是使用JDBC对数据库的事务设置隔离级别的话,也应该是在调用Connection对象的setAutoCommit(false)方法之前。调用Connection对象的setTransactionIsolation(level)即可设置当前链接的隔离级别,至于参数level,可以使用Connection对象的字段:

在JDBC中设置隔离级别的部分代码:

在这里插入图片描述

后记:隔离级别的设置只对当前链接有效。对于使用MySQL命令窗口而言,一个窗口就相当于一个链接,当前窗口设置的隔离级别只对当前窗口中的事务有效;对于JDBC操作数据库来说,一个Connection对象相当于一个链接,而对于Connection对象设置的隔离级别只对该Connection对象有效,与其他链接Connection对象无关。

2. SpringBoot基于AOP控制事务

在springboot中使用时,可以通过注解@Transactional进行类或者方法级别的事务控制,也可以自己通过spring提供的事务管理器手动控制事务

手动事务控制

  @Autowired
  PlatformTransactionManager platformTransactionManager;
  @Autowired
  TransactionDefinition transactionDefinition;
public void testTransaction() {
    //开启事务
    TransactionStatus transactionStatus = platformTransactionManager.getTransaction(transactionDefinition);
    try {
      CategoryInfo categoryInfo1=new CategoryInfo();
      categoryInfo1.setName("分类1");
      categoryDataDao.add(categoryInfo1);
      //模拟发生异常,事务回滚后应该数据库两条记录都没有
      int i=1/0;
      CategoryInfo categoryInfo2=new CategoryInfo();
      categoryInfo2.setName("分类2");
      categoryDataDao.add(categoryInfo2);
      //提交事务
      platformTransactionManager.commit(transactionStatus);
    } catch (Exception e) {
      logger.error("发生异常事务回滚");
      platformTransactionManager.rollback(transactionStatus);
    }
  }

六、分布式事务控制

什么是分布式事务?

分布式系统会把一个应用系统拆分为可独立部署的多个服务,因此需要服务与服务之间的远程协作才能完成实务操作,这种分布式系统环境下由不同的服务之间通过网络远程协作完成事务称为分布式事务。例如:用户注册送积分事务,创建订单减库存事务,银行转账事务。

分布式事务产生的场景

  1. 微服务架构:微服务之间通过远程调用完成事务操作;跨jvm进程产生分布式事务。
  2. 单体系统访问多个数据库实例:数据分布在不同的数据实例,需通过不同的数据库链接去操作数据;跨数据库实例产生分布式事务。
  3. 多服务访问同一个数据库实例:跨jvm进程,两个微服务持有不同的数据库链接进行数据库操作。

分布式事务基础理论CAP理论

1. CAP理论

1.1 理解 CAP

(Consistency一致性、Availability可用性、Partition tolerance分区容忍性)

C-Consistency是什么,如何实现和他的特点?

一致性是指写操作后的读操作可以读取到最新的数据状态,当数据分布在多个节点上,从任意节点读取到的数据都是最新的。

实现:

  1. 写入数据库后将数据同步到从数据库。
  2. 写入主数据库后,在向从数据库同步期间将从数据库锁定,避免读取旧数据。

特点:

  1. 由于存在数据同步过程,写操作的响应有一定延迟
  2. 为保证数据一致性会对资源暂时锁定
  3. 如果请求数据同步失败的节点会返回错误信息,一定不会返回旧数据

A-Availability是什么,如何实现及特点?

可用性是指任何事物操作都可以得到响应结果,且不会出现响应超时或错误。

实现:

  1. 写入主数据库后要将数据同步到从数据库
  2. 由于要保证从数据库的可用性,不可将从数据库中的资源进行锁定
  3. 即使数据没有同步,也要返回查询到的数据,即使是旧数据,如果没旧数据可以按照约定,返回一个默认值

特点:

所有请求都有响应,且不会出现响应超时或者错误。

P-Partition tolerance是什么,如何实现及特点?

通常分布式系统的各个节点部署在不同的子网,这就是网络分区,不可避免的会出现由于网络问题导致节点之间通信失败,此时仍可对外提供服务,这叫分区容忍性。

实现:

  1. 尽量使用异步取代同步操作,例如使用异步方法将数据库从主数据库同步到从数据库,这样节点这件能有效的实现松耦合。
  2. 添加从数据库节点,其中一个从节点挂掉其他从节点提供服务。

热点:

分区容忍性分区是分布式系统具备的基本能力。

1.2 CAP组合方式

  1. AP组合

    放弃一致性,追求分区容忍性和可用性。

  2. CP组合

    放弃可用性,追求一致性和分区容忍性。

  3. CA组合

    放弃人去容忍性,几步进行分区,也不考虑由于网络不通或节点挂掉问题-不是标准的分布式系统

在所有分布式事务场景中不会同时具备CAP三个特性

2. BASE理论
  1. 理解强一致性和最终一致性

  2. Base理论介绍

    Base是Basically Available(基本可用)、Soft state(软状态)和Eventually consistent(最终一致性)三个短语缩写。Base理论是对CAP中AP的一个扩展,通过牺牲一致性来获取可用性,当出现故障允许部分不可用但要保证核心功能可用,允许数据在一段时间内是不一致的,但最终达到一致状态。满足BASE理论的事务,我们称为"柔性事务"。

分布式事务解决方案有4种

分布式事务解决方案–2PC(两阶段提交协议)

什么是2PC?

两阶段提交协议,将整个事务流程分为两个阶段,准备阶段(Prepare phase),提交阶段(commit phase),2是指两个阶段,P是指准备阶段,C是指提交阶段。

准备阶段

提交阶段

2PC-XA方案
2PC-Seata方案

XA实现,数据库层面,由于会在两阶段进行加锁,直到事务提交或回滚才会释放锁资源,因此效率较低。涉及到的组件:TM,RM 由TM负责全局事务的提交和回滚,RM负责分支事务的执行。
seata实现,应用程序层面,由alibaba提供的中间件,不需要再两阶段进行加锁,在数据执行完成之后就提交事务,释放锁资源;若有异常,会由TM通知TC进行事务回滚,效率相比较XA方案实现较高。涉及到的组件:TC,TM,RM 由TC控制发起全局事务的提交和回滚,TM负责收集RM的数据执行信息,通知TC提交全局事务或者回滚事务;TC控制全局事务的提交指的是删除undo_log表中的记录,事务的回滚指的是根据undo_log中的日志记录生成反向sql,通过执行反向sql来实现事务的回滚。全局事务标识要使用@GlobalTransactional注解,本地事务也要使用@Transactional注解,要注意的是@GlobalTransactional注解只在全局事务开始的地方使用。每一个数据源都要有自己的undo_log表,以此来保证本地事务的唯一性。
如果使用2PC的分布式事务解决方案,推荐使用seata实现。
1、搭建一个分布式服务
2、需要两个微服务和一个注册中心
3、安装seataServer
4、在微服务中引入seataServer的依赖
5、在微服务中引入seata所需要使用的两个配置文件,修改配置
6、启动seataServer

分布式事务解决方案–TCC:Try,Confirm,Cancel的缩写

在应用程序控制分布式事务,对代码的侵入性较强,需要开发try,confirm,cancel部分的代码.
tcc-transaction:

Hmily:是一个高性能分布式事务TCC开源框架,基于java1.8开发,支持Dubbo和SpringCloud
使用时需要在事务发起方的try方法上加注解@Hmily,此注解可以表明本方法是try方法,在注解中指定Confirm和Cancel的方法名,如果跨微服务使用Feign调用时,需要在注解上加@Hmily注解,将全局事务id传递过去,只有基于一个全局事务id才能对一个全局事务进行控制。
空回滚:没有执行try,却执行了cancel
悬挂:先执行了cancel,后执行了try,导致在try中的预留资源无法再被处理
幂等:Try,Confirm,Cancel接口都需要做到幂等,避免重复使用或者释放资源,导致数据不一致。

ByteTcc:

EasyTransaction

Seata也支持TCC,但是Seata的TCC模式暂不提供对SpringCloud的支持。

分布式事务解决方案–可靠消息最终一致性:

通过消息队列等中间件实现。
需要解决的问题:
1、本地事务与消息发送的原子性问题
2、事务参与方接收消息的可靠性(在消息接收失败的情况下,可以重试,保证一定能够接收到消息)
3、消息的重复消费问题(对已经消费过的消息不要进行重复消费)

RocketMQ实现 可靠消息最终一致性

分布式事务解决方案–最大努力通知

2PC:数据库层面控制,需要建立undo_log表,如果跨系统不适用。
TCC:对应用代码程序的侵入性比较强,跨系统不适用。
可靠消息最终一致性 和 最大努力通知 都需要借助消息队列,在跨系统的情况下不可能直接监听外部系统的消息队列或者被外部系统监听自己的消息队列,
最大努力通知:内部系统交互的时候,可以直接通过消息队列通知消息,但是在设计外部系统的时候,需要在内部开发一个通知程序,监听消息队列,然后通过通知程序再通知到接收通知方。

六、什么是JVM(Java Virtual Machine,Java虚拟机)?

JVM是JRE的一部分。它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。JVM有自己完善的硬件架构,如处理器、堆栈、寄存器等,还具有相应的指令系统。Java语言最重要的特点就是跨平台运行。使用JVM就是为了支持与操作系统无关,实现跨平台

Java虚拟机,字节码文件可以运行在jvm中,支持了java语言跨平台得特性,一次编译到处运行。从软件层面屏蔽了底层硬件指令层面细节。屏蔽不同系统之间的差异性

JVM内存结构

在这里插入图片描述

粗略分来,JVM的内部体系结构分为三部分,分别是:类装载器(ClassLoader)子系统,运行时数据区,和执行引擎。

类装载器

每一个Java虚拟机都由一个类加载器子系统(class loader subsystem),负责加载程序中的类型(类和接口),并赋予唯一的名字。每一个Java虚拟机都有一个执行引擎(execution engine)负责执行被加载类中包含的指令。JVM的两种类装载器包括:启动类装载器和用户自定义类装载器,启动类装载器是JVM实现的一部分,用户自定义类装载器则是Java程序的一部分,必须是ClassLoader类的子类。

执行引擎:

它或者在执行字节码,或者执行本地方法

主要的执行技术有:解释,即时编译,自适应优化、芯片级直接执行其中解释属于第一代JVM,即时编译JIT属于第二代JVM,自适应优化(目前Sun的HotspotJVM采用这种技术)则吸取第一代JVM和第二代JVM的经验,采用两者结合的方式 。

自适应优化:开始对所有的代码都采取解释执行的方式,并监视代码执行情况,然后对那些经常调用的方法启动一个后台线程,将其编译为本地代码,并进行仔细优化。若方法不再频繁使用,则取消编译过的代码,仍对其进行解释执行。

JVM运行时数据区

因为jvm运行时的数据区对我们开发来说还是特别重要要掌握的知识所以单拎开来西说下。

  • 方法区域(Method Area)

    线程共享,它用于存储已被虚拟机加载的类信息常量静态变量、即时编译器编译后的代码等数据。

    在Sun JDK中这块区域对应的为PermanetGeneration,又称为持久代。当JVM的类装载器加载.class文件,并进行解析,把解析的类型信息放入方法区。

    方法区域存放了所加载的类的信息(名称、修饰符等)、类中的静态变量、类中定义为final类型的常量、类中的Field信息、类中的方法信息,当开发人员在程序中通过Class对象中的getName、isInterface等方法来获取信息时,这些数据都来源于方法区域,同时方法区域也是全局共享的,在一定的条件下它也会被GC,当方法区域需要使用的内存超过其允许的大小时,会抛出OutOfMemory的错误信息。

  • 堆(Heap)

    是Java虚拟机所管理的内存中最大的一块,在虚拟机启动时创建,此内存区域的唯一目的就是存放对象实例

    存放所有程序在运行时创建的对象,它是JVM用来存储对象实例以及数组值的区域,可以认为Java中所有通过new创建的对象的内存都在此分配,Heap中的对象的内存需要等待GC进行回收。

    堆是JVM中所有线程共享的,因此在其上进行对象内存的分配均需要进行加锁,这也导致了new对象的开销是比较大的。

  • JavaStack(java的栈)

    JVM栈是线程私有的,每个线程创建的同时都会创建JVM栈,JVM栈中存放的为当前线程中局部基本类型的变量(java中定义的八种基本类型:boolean、char、byte、short、int、long、float、double)、部分的返回结果以及Stack Frame,非基本类型的对象在JVM栈上仅存放一个指向堆上的地址,生命周期与线程相同。

    虚拟机栈描述的是Java方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧(Stack Frame)用于存储局部变量表操作栈动态链接方法出口等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。

    虚拟机只会直接对Java stack执行两种操作:以帧为单位的压栈或出栈

    每个帧代表一个方法,Java方法有两种返回方式,return和抛出异常,两种方式都会导致该方法对应的帧出栈和释放内存。

    帧的组成:局部变量区(包括方法参数和局部变量,对于instance方法,还要首先保存this类型,其中方法参数按照声明顺序严格放置,局部变量可以任意放置),操作数栈,帧数据区(用来帮助支持常量池的解析,正常方法返回和异常处理)。

  • ProgramCounter(程序计数器)

    线程私有,是一块较小的内存空间,它的作用可以看做是当前线程所执行的字节码的行号指示器

    每一个线程都有它自己的PC寄存器,也是该线程启动时创建的。PC寄存器的内容总是指向下一条将被执行指令的地址,这里的地址可以是一个本地指针,也可以是在方法区中相对应于该方法起始指令的偏移量。

    若thread执行Java方法,则PC保存下一条执行指令的地址。若thread执行native方法,则Pc的值为undefined

  • Nativemethodstack(本地方法栈):

    存储本地方法调用的状态,保存native方法进入区域的地址

    依赖于本地方法的实现,如某个JVM实现的本地方法借口使用C连接模型,则本地方法栈就是C栈,可以说某线程在调用本地方法时,就进入了一个不受JVM限制的领域,也就是JVM可以利用本地方法来动态扩展本身。

    简要概括

    • 堆【线程共享】 :对象实例
    • 方法区【线程共享】 :类信息、常量、静态变量、编译后的代码
    • :局部变量、操作栈、动态链接、方法出口
    • 程序计数器 :字节码的行号指示器
    • 本地方法栈 :JVM使用到的native方法服务(java调用非java代码的接口)

JVM垃圾回收

Sun的JVMGenerationalCollecting(垃圾回收)原理是这样的:把对象分为年青代(Young)、年老代(Tenured)、持久代(Perm),对不同生命周期的对象使用不同的算法。(基于对对象生命周期分析)

通常我们说的JVM内存回收总是在指堆内存回收,确实只有堆中的内容是动态申请分配的,所以以上对象的年轻代和年老代都是指的JVM的Heap空间,而持久代则是之前提到的MethodArea,不属于Heap。

GC的基本原理:将内存中不再被使用的对象进行回收,GC中用于回收的方法称为收集器,由于GC需要消耗一些资源和时间,Java在对对象的生命周期特征进行分析后,按照新生代、旧生代的方式来对对象进行收集,以尽可能的缩短GC对应用造成的暂停

(1)对新生代的对象的收集称为minor GC;

(2)对旧生代的对象的收集称为Full GC;

(3)程序中主动调用System.gc()强制执行的GC为Full GC。

不同的对象引用类型, GC会采用不同的方法进行回收,JVM对象的引用分为了四种类型:

(1)强引用:默认情况下,对象采用的均为强引用(这个对象的实例没有其他对象引用,GC时才会被回收)

(2)软引用:软引用是Java中提供的一种比较适合于缓存场景的应用(只有在内存不够用的情况下才会被GC)

(3)弱引用:在GC时一定会被GC回收

(4)虚引用:由于虚引用只是用来得知对象是否被GC

  • Young(年轻代)

    年轻代分三个区。一个Eden区,两个Survivor区。大部分对象在Eden区中生成。当Eden区满时,还存活的对象将被复制到Survivor区(两个中的一个),当这个Survivor区满时,此区的存活对象将被复制到另外一个Survivor区,当这个Survivor去也满了的时候,从第一个Survivor区复制过来的并且此时还存活的对象,将被复制年老区(Tenured。需要注意,Survivor的两个区是对称的,没先后关系,所以同一个区中可能同时存在从Eden复制过来对象,和从前一个Survivor复制过来的对象,而复制到年老区的只有从第一个Survivor去过来的对象。而且,Survivor区总有一个是空的。

  • Tenured(年老代)

    年老代存放从年轻代存活的对象。一般来说年老代存放的都是生命期较长的对象。

  • Perm(持久代)

    用于存放静态文件,如今Java类、方法等。持久代对垃圾回收没有显著影响,但是有些应用可能动态生成或者调用一些class,例如Hibernate等,在这种时候需要设置一个比较大的持久代空间来存放这些运行过程中新增的类。持久代大小通过-XX:MaxPermSize=进行设置。

简要概括如下

  • 对象先在Eden区,Eden区空间不够时进行新生代GC(minor GC)
  • 大对象和长期存活的对象进入老年代
  • JVM为每个对象设置了计数器,经过1次新生代GC则进入幸存者区,达到年龄阈值则进入老年区
  • 幸存者区中年龄一致的对象所占内存大小,大于幸存者区空间一半时,则大于等于此年龄的对象全部进入老年代
  • 老年代GC(full GC)通常伴随着一次新生代GC,但不绝对

术语说明

  • Young Generation(新生代):分为:Eden区和Survivor区,Survivor区有分为大小相等的From Space和To Space。
  • Old Generation(老年代): Tenured区,当 Tenured区空间不够时, JVM 会在Tenured区进行 major collection。
  • Minor GC:新生代GC,指发生在新生代的垃圾收集动作,因为java对象大多都具备朝生夕死的特性,所以Minor GC非常频繁,一般回收速度也比较快。
  • Major GC:发生老年代的GC,对整个堆进行GC。出现Major GC,经常会伴随至少一次Minor GC(非绝对)。MajorGC的速度一般比minor GC慢10倍以上。
  • Full GC:整个虚拟机,包括永久区、新生区和老年区的回收。

七、HashMap实现原理

在这里插入图片描述

HashMap是基于哈希表对Map接口的实现,HashMap具有较快的访问速度,但遍历顺序不确定。以key-value形式存储数据,提供所有可选的映射操作,并允许null值和null建,HashMap并非线程安全,当存在多个线程同时写入HashMap时,可能会导致数据不一致。

jdk1.7:头插法

**数组:**采用一段连续得存储单元来存储数据,特点:查询快,增删慢(java.util.ArrayList)

**链表:**是一种物理存储单元上非连续、非顺序得存储结构,特点:插入、删除时间复杂度0(1),查找遍历时间复杂度o(N),插入快 查找慢。(java.util.LinkedList)

**哈希算法:**也叫散列,就是把任意长度值key通过散列算法变换成固定长度得key地址。通过这个地址进行访问数据结构。

**put方法:**通过哈算法获取下标 ,数组每个单元包含一个对象里有四个元素

​ entry{key,value,hash,next}

**hash碰撞:**当hash计算出的下标位置存在数据时就叫hash碰撞,jdk1.7 头插法,后入的next指向前一个。

**get方法:**通过对keyhash计算下标,判断key是否相等,不相等,判断next是否为空,不为空则拿到next指向的key判断是否相等,相等则拿到value。

链表弊端查询慢,jdk1.7中链表长度没有上限导致查询性能慢

jdk1.8 红黑树 解决链表过长查询慢的问题,带来问题 阈值8

扩容2的等次幂

字段认识:

**loadFactor:**负载因子,默认值为0.75

**threshold:**阈值,表示所能容纳的键值对的临界值,threshold计算公式为:数组长度*负载因子

**size:**HashMap中实际存在的键值对数量

**modCount:**记录HashMap内部结构发生变化的次数。

HashMap默认容量Initial_Capacity=16

HashMap采用了数组+ 链表+红黑树的存储结构,数组部分称为hash桶,当链表长度大于等于8时,链表数据将以红黑树的形式进行存储,当长度降到6是,转成链表。

每个node节点存储着用来定位数据索引位置的hash值,k值,v值以及指向下链表下一个节点的node<k,v> next节点组成,Node是HashMap的内部类,实现了Map.Entry接口,本质是一个键值对。

当向HashMap中插入数据时,首先要确定在哈希桶数组中的位置。

如何确定Node存储的位置,以添加key键‘e’为例?

HashMap首先调用hashCode()方法,获取键key的hashCode值h(101),然后对其进行高位运算:

将h右移16位以取得h的高16位,与原h的低16位进行异或运算(结果为101),最后将得到的h值与(table.length-1)进行与运算获得该对象的保留位以计算下标

存储hashMap.put(K,V);查找hashMap.get(K);

当插入第二个存在的key对象时,将新值赋值给key,当插入的Node键值对大小超过Threshold(0.75*16)阈值时,HashMap将新建一个桶数组,并重新赋值。

红黑树解决了链表过长查询慢的问题,和hash冲突问题。

Java 7插入链表头部,是考虑到新插入的数据,更可能作为热点数据被使用,放在头部可以减少查找时间。

Jdk1.8尾插法主要是为了安全,防止环化

八、多线程

线程池

使用工具excutors创建

ExecutorService executorService = Executors.newSingleThreadExecutor();
ExecutorService executorService = Executors.newFixedThreadPool(10);
ExecutorService executorService = Executors.newCachedThreadPool();

实现多线程

九、StringBuilder、StringBuffer、String区别

1.速度比较

String为字符串常量,而StringBuilder和StringBuffer均为字符串变量,即String对象一旦创建之后该对象是不可更改的,但后两者的对象是变量,是可以更改的。

和 String 类不同的是,StringBuffer 和 StringBuilder 类的对象能够被多次的修改,并且不产生新的未使用对象

String 为字符串常量,一旦创建该对象就不会改变,String a=1,a=1+2;相当于重新创建对象赋值

而StringBuilder和StringBuffer的对象是变量,对变量进行操作就是直接对该对象进行更改,而不进行创建和回收的操作,所以速度要比String快很多。

2.线程安全

在线程安全上,StringBuilder是线程不安全的,而StringBuffer是线程安全的

如果一个StringBuffer对象在字符串缓冲区被多个线程使用时,StringBuffer中很多方法可以带有synchronized关键字,所以可以保证线程是安全的,但StringBuilder的方法则没有该关键字,所以不能保证线程安全,有可能会出现一些错误的操作。所以如果要进行的操作是多线程的,那么就要使用StringBuffer,但是在单线程的情况下,还是建议使用速度比较快的StringBuilder。

3. 总结

String:适用于少量的字符串操作的情况

StringBuilder:适用于单线程下在字符缓冲区进行大量操作的情况

StringBuffer:适用多线程下在字符缓冲区进行大量操作的情况

Redis

redis是什么?

redis是一个开源的使用ANSI C语言编写、遵守BSD协议、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。

redis的特性?

  • Redis支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用。
  • Redis支持五种数据类型:string(字符串),hash(哈希),list(列表),set(集合)及zset(sorted set:有序集合)。
  • Redis支持数据的备份,即master-slave模式的数据备份。集群
  • 支持事务

Redis的事务:单条命名保证原子性,但是事务不保证原子性。

Redis事务中没有隔离级别的概念 :所有命令在事务中,并没有直接被执行,只是发起命令执行时才执行exec。

  • 开启事务(multi)
  • 命令入队
  • 执行事务(exec)
  • 取消事务(DISCARD) 事务对列命令不执行

Redis监视测试:使用watch加锁,在提交事务数据被修改则不会执行修改操作

Redis的持久化机制

Redis是基于内存存储的数据库,为了保证更有效地避免因进程退出造成的数据丢失问题,Redis提供了两种持久化机制,RDB和AOF机制,当服务重启时利用之前的持久化文件即可实现数据恢复

RDB(RedisDataBase)持久机制

RDB机制也称Snapshotting(快照)在指定的时间间隔内将内存中的数据集以快照文件方式写入磁盘也就是快照,恢复时是将快照文件读到内存里。

Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入到一个临时文件,待持久化过程结束了,再用这个临时文件替换上次持久化好的文件。整个过程中,主进程不进行任何IO操作,确保了极高的性能。如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那RDB方式比AOF方式更加高效。RDB的缺点是无法实现秒级的备份最后一次持久化后的数据可能会丢失。默认是RDB。

RDB保存文件是dump.rdb

#触发rdb的条件
save 900 1  #900秒改变了1个key
save 300 10 #300秒改变了10个key
save 60 10000 #60秒改变了10000个key
flushall #触发rdb
#退出redis 触发生成rdb文件

如何恢复rdb文件?

  1. 只需要将rdb文件放在Redis启动目录,redis启动会自动检查dump.rdb恢复其中数据
  2. 查看需要恢复数据存放rdb文件位置(config get dir)

优点:

  1. 适合大规模的数据恢复
  2. 如果对数据的完整性要求不高

缺点:

  1. 需要一定的时间间隔进行操作,如果redis意外宕机最后一次修改数据就不存在了
  2. fork子进程时会占用一定的内存空间
AOF(Append-Only File)持久机制

AOF:将所有写操作命令记录下来,恢复的时候就把这个文件命令全部执行一遍

以日志的形式记录每个写操作,将Redis执行过的所有操作记录下来(读操作不记录),只许追加文件,但不许改写文件,redis启动之初会读取该文件重新构建数据,将日志文件内容从前到后执行一次完成数据恢复工作

AOF保存的是appendonly.aof

aof默认不开启,需要手动进行配置

如果aof文件有错,redis启动不起来,需要修复文件,redis提供了一个工具redis-check-aof --fix

同时开启两种方式,redis重启时优先加载aof文件恢复数据

优点:

  1. 每一次修改都同步文件的完整性会很好
  2. 每秒同步一次,可能会丢失一秒的数据
  3. 从不同步,效率最高

缺点:

  1. 相对于数据文件来说,aof远远大于rdb,修复速度也比rdb慢
  2. aof运行效率比rdb慢,所以redis默认rdb
#开启aof
appendonly yes
#触发aof机制条件
appendfsync always   #每次收到写命令就立即强制写入磁盘,最慢的,但是保证完全的持久化,不推荐使用
appendfsync everysec #每秒钟强制写入磁盘一次,在性能和持久化方面做了 很好的折中 推荐
appendfsync no       #完全依赖os,性能最好,持久化没保证。

#AOF重写 目的:缩小AOF日志文件大小,优化aof文件,去除没有任何意义的命令文件。
#1.自动触发
	#配置文件中 触发时两个条件必须全部满足
	auto-aof-rewrite-percentage 100	#当前文件较上一次重写时增加了100%
	auto-aof-rewrite-min-size 64mb	#当前文件大小达到  64mb		
#手动执行命令触发
	bgrewriteaof

	config set appendonly  yes		#在redis运行的时候开启AOF机制
	config set save ""   		   # 在redis运行的时候关闭RDB机制

Redis缓存穿透和雪崩

服务高可用问题

缓存穿透(查不到)

概念

很多用户想要查询一个数据,发现缓存中没有,于是向持久层数据库查询,数据库也没有,这会给持久层数据库造成很大压力,这就出现了缓存穿透。

解决方案

1. 布隆过滤器

是一种数据结构,对所有可能查询的参数以hash形式存储,在控制层先进行校验,不符合则丢弃,从而避免了对底层存储系统的查询压力;

2. 缓存空对象

当存储层不命中后,即使返回的空对象也将其缓存起来,同时会设置一个过期时间,之后在访问这个数据将会从缓存中获取,保护了后端数据源

解决方案存在的问题

  1. 如果空值被缓存起来,意味着缓存需要更多空间存储更多的键
  2. 即使设置了过期时间,还是会存在缓存层和存储层数据会有一段时间不一致。
缓存击穿(查询量太大)

概念

缓存击穿:是指一个key非常热点,再不停的扛着大并发,大并发集中对这个key进行访问,当这个key失效瞬间,持续的大并发就会穿破缓存,直接请求数据库。

解决方案

设置热点数据永不过期

加互斥锁

分布式锁:在缓存和数据库间使用分布式锁,保证对每个key同时只有一个线程去查询后端服务,其他线程没有获得分布式锁的权限,因此只需要等待,这种方式将高并发压力转移到了分布式锁,因此对分布式锁要求很高。

缓存雪崩

概念

缓存雪崩:是指在某个时间段,缓存集体失效过期,或者redis宕机

解决方案

redis高可用

限流降级

在缓存失效后,通过加锁或者队列来控制读数据库的写缓存的线程数,

数据预热

在正式部署之前,吧可能的数据预先访问一遍,使其加载到缓存中。即在即将发生大并发前手动触发加载缓存不同的key,设置不同的过期时间,让缓存失效时间尽量均匀。

十、Spring Cloud

乐观锁和悲观锁

  • 悲观锁:很悲观,认为什么时候都会出问题,无论做什么都会加锁
  • 乐观锁:很乐观,认为什么时候都不会出现问题,所以不上锁,跟新数据的时候去判断一下,在此期间是否有人修改该数据。

乐观锁

乐观锁是相对于悲观锁而言,是为了避免数据库幻读,业务处理时间过长等原因引起的数据处理错误的一种机制,但乐观锁不会刻意使用数据库本身的锁机制,而是依据数据本身来保证数据的正确性。

**乐观锁的实现:**版本号控制:一般是在数据表中加上一个数据版本号 version 字段,表示数据被修改的次数。当数据被修改时,version 值会+1。当线程A要更新数据值时,在读取数据的同时也会读取 version 值,在提交更新时,若刚才读取到的 version 值与当前数据库中的 version 值相等时才更新,否则重试更新操作,直到更新成功。

悲观锁

悲观锁的实现,往往依靠数据库提供的锁机制。在数据库中,悲观锁的流程如下:

  1. 在对记录进行修改前,先尝试为该记录加上排他锁(exclusive locks)。
  2. 如果加锁失败,说明该记录正在被修改,那么当前查询可能要等待或者抛出异常。具体响应方式由开发者根据实际需要决定。
  3. 如果成功加锁,那么就可以对记录做修改,事务完成后就会解锁了。
  4. 期间如果有其他对该记录做修改或加排他锁的操作,都会等待解锁或直接抛出异常。

具体实现:记录修改前,先通过 sql 中添加for update 的方式进行加锁,然后再进行修改。这就是比较典型的悲观锁策略。

响应效率:如果需要非常高的响应速度,建议采用乐观锁方案,成功就执行,不成功就失败,不需要等待其他并发去释放锁。乐观锁并未真正加锁,效率高。一旦锁的粒度掌握不好,更新失败的概率就会比较高,容易发生业务失败。
2️⃣冲突频率:如果冲突频率非常高,建议采用悲观锁,保证成功率。冲突频率大,选择乐观锁会需要多次重试才能成功,代价比较大。
3️⃣重试代价:如果重试代价大,建议采用悲观锁。悲观锁依赖数据库锁,效率低。更新失败的概率比较低。
4️⃣乐观锁如果有人在你之前更新了,你的更新应当是被拒绝的,可以让用户从新操作。悲观锁则会等待前一个更新完成。这也是区别。

Eques()和==区别

初步认识equals与==的区别:
  1. ==是判断两个变量或实例是不是指向同一个内存空间,equals是判断两个变量或实例所指向的内存空间的值是不是相同
  2. ==是指对内存地址进行比较 , equals()是对字符串的内容进行比较
  3. ==指引用是否相同, equals()指的是值是否相同

SQL查重去重

Redis缓存及项目实现

MySQL优化及Orcle区别

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值