spring

BeanDefinition对象:我们在 XML 中定义的 bean标签,Spring 解析 bean 标签成为⼀个 JavaBean,这个JavaBean 就是 BeanDefinition

SpringBean的⽣命周期图

一 IOC

IOC 的优缺点

  • 优点:实现组件之间的解耦,提高程序的灵活性和可维护性。
  • 缺点:生成一个对象的步骤变复杂了,生成因为是使用反射编程,在效率上有些损耗。但相对于IoC提高的维护性和灵活性来说,这点损耗是微不足道的,除非某对象的生成对效率要求特别高。

Bean 的生命周期:实例化、属性填充、初始化、使用、销毁。 
主要过程:
定位Bean元数据:可在classpath、filesystem、network等位置;Bean可通过XML(beans、bean元素等)、Java注解(Congifuration、Bean注解等)、Java代码三种方式定
加载Bean元数据:读入后创建成BeanDefinition
注册:根据BeanDefinition创建Bean对象并注册到IoC容器(即ApplicationContext)。注意并不是一定都立马创建实例,未被用到的会延迟到等被使用时再创建。
依赖注入:对Bean中依赖其他Bean实例的属性赋值(AbstractAutoWireCapableBeanFactory.populateBean)

以 ClasspathXmlApplicationContext 为例,深⼊源码说明 IoC 容器的初始化流程

AbstractApplicationContext类refresh⽅法是IOC整个源码的核心

@Override
public void refresh() throws BeansException, IllegalStateException {
            synchronized (this.startupShutdownMonitor) {
    // 第⼀步:刷新前的预处理
    prepareRefresh();
    /*
第⼆步:
获取BeanFactory;默认实现是DefaultListableBeanFactory
加载BeanDefition 并注册到 BeanDefitionRegistry
*/
 ConfigurableListableBeanFactory beanFactory =
obtainFreshBeanFactory();
// 第三步:BeanFactory的预准备⼯作(BeanFactory进⾏⼀些设置,⽐如context的类加
载器等)
prepareBeanFactory(beanFactory);
try {
// 第四步:BeanFactory准备⼯作完成后进⾏的后置处理⼯作
postProcessBeanFactory(beanFactory);
// 第五步:实例化并调⽤实现了BeanFactoryPostProcessor接⼝的Bean
invokeBeanFactoryPostProcessors(beanFactory);
// 第六步:注册BeanPostProcessor(Bean的后置处理器),在创建bean的前后等执
⾏
registerBeanPostProcessors(beanFactory);
// 第七步:初始化MessageSource组件(做国际化功能;消息绑定,消息解析);
initMessageSource();
// 第⼋步:初始化事件派发器
initApplicationEventMulticaster();
// 第九步:⼦类重写这个⽅法,在容器刷新的时候可以⾃定义逻辑
onRefresh();
// 第⼗步:注册应⽤的监听器。就是注册实现了ApplicationListener接⼝的监听器
bean
registerListeners();
/*
第⼗⼀步:
初始化所有剩下的⾮懒加载的单例bean
初始化创建⾮懒加载⽅式的单例Bean实例(未设置属性)
填充属性
初始化⽅法调⽤(⽐如调⽤afterPropertiesSet⽅法、init-method⽅法)
调⽤BeanPostProcessor(后置处理器)对实例bean进⾏后置处
*/
finishBeanFactoryInitialization(beanFactory);
/*
第⼗⼆步:
完成context的刷新。主要是调⽤LifecycleProcessor的onRefresh()⽅法,并且发布事
件 (ContextRefreshedEvent)
*/
finishRefresh();
}
第2节 BeanFactory创建流程
2.1 获取BeanFactory⼦流程
时序图如下
2.2 BeanDefinition加载解析及注册⼦流程
(1)该⼦流程涉及到如下⼏个关键步骤
Resource定位:指对BeanDefinition的资源定位过程。通俗讲就是找到定义Javabean信息的XML⽂
件,并将其封装成Resource对象。
BeanDefinition载⼊ :把⽤户定义好的Javabean表示为IoC容器内部的数据结构,这个容器内部的数
据结构就是BeanDefinition。
注册BeanDefinition到 IoC 容器
(2)过程分析
......
}
}

1 获取BeanFactory⼦流程

 2 BeanDefinition加载解析及注册⼦流程

(1)该⼦流程涉及到如下⼏个关键步骤

Resource定位:指对BeanDefinition的资源定位过程。通俗讲就是找到定义Javabean信息的XML⽂件,并将其封装成Resource对象。
BeanDefinition载⼊ :把⽤户定义好的Javabean表示为IoC容器内部的数据结构,这个容器内部的数据结构就是BeanDefinition。注册BeanDefinition到 IoC 容器

注册流程结束,我们发现,所谓的注册就是把封装的 XML 中定义的 Bean信息封装为
BeanDefinition 对象之后放⼊⼀个Map中,BeanFactory 是以 Map 的结构组织这些 BeanDefinition

/** Map of bean definition objects, keyed by bean name. */
private final Map<String, BeanDefinition> beanDefinitionMap = new
ConcurrentHashMap<>(256);

3 Bean创建流程 

doCreateBean⽅法(创建Bean实例,此时尚未设置属性)--->populateBean(填充属性) getSingleton()

4 lazy-init 延迟加载机制原理

普通 Bean 的初始化是在容器启动初始化阶段执⾏的,⽽被lazy-init=true修饰的 bean 则是在从容器⾥第⼀次进⾏context.getBean() 时进⾏触发。Spring 启动的时候会把所有bean信息(包括XML和注解)解析转化成Spring能够识别的BeanDefinition并存到Hashmap⾥供下⾯的初始化时⽤,然后对每个BeanDefinition 进⾏处理,如果是懒加载的则在容器初始化阶段不处理,其他的则在容器初始化阶段进⾏初始化并依赖注⼊。

public void preInstantiateSingletons() throws BeansException {
// 所有beanDefinition集合
List<String> beanNames = new ArrayList<String>(this.beanDefinitionNames);
// 触发所有⾮懒加载单例bean的初始化
for (String beanName : beanNames) {
// 获取bean 定义
RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
// 判断是否是懒加载单例bean,如果是单例的并且不是懒加载的则在容器创建时初始化
if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
// 判断是否是 FactoryBean
if (isFactoryBean(beanName)) {
final FactoryBean<?> factory = (FactoryBean<?>)
getBean(FACTORY_BEAN_PREFIX + beanName);
boolean isEagerInit;
if (System.getSecurityManager() != null && factory instanceof
SmartFactoryBean) {
isEagerInit = AccessController.doPrivileged(new
PrivilegedAction<Boolean>() {
总结
对于被修饰为lazy-init的bean Spring 容器初始化阶段不会进⾏ init 并且依赖注⼊,当第⼀次
进⾏getBean时候才进⾏初始化并依赖注⼊
对于⾮懒加载的bean,getBean的时候会从缓存⾥头获取,因为容器初始化阶段 Bean 已经
初始化完成并缓存了起来
第5节 Spring IoC循环依赖问题
5.1 什么是循环依赖
循环依赖其实就是循环引⽤,也就是两个或者两个以上的 Bean 互相持有对⽅,最终形成闭环。⽐如A
依赖于B,B依赖于C,C⼜依赖于A。
@Override
public Boolean run() {
return ((SmartFactoryBean<?>) factory).isEagerInit();
}
}, getAccessControlContext());
}
}else {
/*
如果是普通bean则进⾏初始化并依赖注⼊,此 getBean(beanName)接下来触发的逻辑
和
懒加载时 context.getBean("beanName") 所触发的逻辑是⼀样的
*/
getBean(beanName);
}
}
}
}

总结
对于被修饰为lazy-init的bean Spring 容器初始化阶段不会进⾏ init 并且依赖注⼊,当第⼀次
进⾏getBean时候才进⾏初始化并依赖注⼊
对于⾮懒加载的bean,getBean的时候会从缓存⾥头获取,因为容器初始化阶段 Bean 已经
初始化完成并缓存了起来 

二 AOP

【Spring AOP(2)篇】原理及两种实现方式(cglib&jdk动态代理)_spring 默认的 jdk 动态代理-CSDN博客

 Spring 实现AOP思想使⽤的是动态代理技术
默认情况下,Spring会根据被代理对象是否实现接⼝来选择使⽤JDK还是CGLIB。当被代理对象没有实现任何接⼝时,Spring会选择CGLIB。当被代理对象实现了接⼝,Spring会选择JDK官⽅的代理技术,不过我们可以通过配置的⽅式,让Spring强制使⽤CGLIB。

决定创建代理对象是⽤JDK Proxy,还是⽤ Cglib 了,最简单的从使⽤⽅⾯使⽤来说:设置
proxyTargetClass=true强制使⽤Cglib 代理,什么参数都不设并且对象类实现了接⼝则默认⽤JDK 代理,如果没有实现接⼝则也必须⽤Cglib 

三 BEAN循环依赖

spring循环依赖及解决方法_spring循环依赖及解决方式-CSDN博客

循环依赖而栈溢出

Spring 的循环依赖的理论依据基于 Java 的引⽤传递,当获得对象的引⽤时,对象的属性是可以延
后设置的,但是构造器必须是在获取引⽤之前

Spring中循环依赖场景有:
构造器的循环依赖(构造器注⼊)
Field 属性的循环依赖(set注⼊)
其中,构造器的循环依赖问题⽆法解决,只能拋出 BeanCurrentlyInCreationException 异常,在解决属性循环依赖时,spring采⽤的是提前暴露对象的⽅法。

单例 bean 构造器参数循环依赖(⽆法解决)为什么=因为无法实例化 无法放到缓存中
prototype 原型 bean循环依赖(⽆法解决)
对于原型bean的初始化过程中不论是通过构造器参数循环依赖还是通过setXxx⽅法产⽣循环依
赖,Spring都 会直接报错处理。

总结:Spring 不⽀持原型 bean 的循环依赖。
单例bean通过setXxx或者@Autowired进⾏循环依赖

循环依赖总结一下(假设A,B之间循环依赖):
一级缓存singletonObject,也就是常说的单例池,是个Map
二级缓存earlySingletonObjects,也就是提前一点的单例池,哈哈,字面翻译,也是Map
三级缓存singletonFactories,这个Map有点特殊,因为这个Map的value存放的是一个lambda表达式
1、单例池不能存放原始对象,只能存放经过完整生命周期的对象,也就是java bean
2、A,B在注入属性都会执行一个addSingletonFactory方法,这个方法里面三级缓存就出现了,三级缓存put了key为beanName,value为一个lambda表达式
3、其实最容易绕晕的地方是,当B注入属性A的时候,执行populateBean注入一个bean的属性的时候会执行getSingleton这个方法,一定要记得!!populateBean方法体中没有直接调用getSingleton这个方法,但一定要记得,执行了这个方法
4、getSingleton这个方法,会依次到一级缓存,二级缓存,三级缓存中get(beanName),很显然当B注入A属性的时候,一级,二级里面都没有内容,只有三级有,这时会执行lambda表达式,lambda表达式的作用就是生成代理对象!!然后把生成的代理对象存入二级缓存,并返回这个代理对象,B就会得到这个代理对象A,B就会认为这个代理对象A就是A的最后的bean对象,因此也就完成了对A的属性注入这步操作,接着依次执行B后续的操作,最后就完成了B的生命周期,B就成功变成了bean对象,B也就完成了使命
5、当B完成使命之后,A就会继续注入B,这时就会注入属性成功了,接下来开始执行AOP操作,因为上一步中A已经生成了代理对象A,也就是相当于完成了AOP,所以B就不执行AOP操作了,此时A就会执行最后一步操作,将代理对象A放入到单例池中去,这时A就会执行方法getSingleton,从二级缓存中获得了代理对象A,最后将其存入单例池,也就是一级缓存!好了,现在A和B都放入了单例池,圆满结束!!!!

Map<String, Object> singletonObjects:一级缓存。存放完全初始化好了(也即完全创建完成了)的单例bean,从该缓存中取出的单例bean可直接使用。
Map<String, Object> earlySingletonObjects:二级缓存。存放实例化了但尚未进行Bean属性填充等初始化操作的单例bean(Spring中称为earlyExposteObject,提前暴露的对象),用于解决循环依赖。
Map<String, ObjectFactory<?>> singletonFactories:三级缓存。存放单例Bean的工厂对象,用于解决循环依赖。

所以Spring引入三级缓存的原因:为了在没有循环依赖的情况下延迟代理对象的创建以使 Bean 的创建符合 Spring 的设计原则。【Spring的设计原则是在对象初始化完成后才为对象创建代理对象,而在循环依赖场景下不得不提前(实例化后初始化前)创建代理否则注入的是错误的对象】,但Spring在实例化一个对象后并不知道会不会有循环依赖,所以引入了第三级缓存来确保没有循环依赖时代理对象的延迟创建。

四 FactoryBean 和 BeanFactory

BeanFactory接⼝是容器的顶级接⼝,定义了容器的⼀些基础⾏为,负责⽣产和管理Bean的⼀个⼯⼚,具体使⽤它下⾯的⼦接⼝类型,⽐如ApplicationContext;

FactoryBean 一般情况下,Spring通过反射利用bean的class属性指定实现类来实例化bean。在某些情况下,实例化bean的过程比较复杂,如果按照传统的方式,则需要在<bean>中提供大量的配置信息,配置方式的灵活性是受限的,这时采用编码的方式可能会得到一个简单的方案。

Spring为此提供了一个FactoryBean工厂类接口,用户可以通过实现该接口定制实例化bean的逻辑。FactoryBean是一个能生产或修饰对象生成的工厂Bean,当我们实现了FactoryBean接口,重写getObject()方法并返回一个实例时,Spring会按照我们指定的内容去注册Bean,来达到定制实例化Bean的效果。当配置文件中<bean>的class属性配置的实现类是FactoryBean时,通过getBean()方法返回的不是FactoryBean本身,而是FactoryBean#getObject()方法所返回的对象,相当于FactoryBean#getObject()代理了getBean()方法。如果要获取MyFactoryBean工厂本身实例,那么需要在名称前面加上'&'符号

五 后置处理器

Spring提供了两种后处理bean的扩展接⼝,分别为 BeanPostProcessor 和BeanFactoryPostProcessor,两者在使⽤上是有所区别的。
工厂初始化(BeanFactory)—> Bean对象在BeanFactory初始化之后可以使⽤BeanFactoryPostProcessor进⾏后置处理做⼀些事情
在Bean对象实例化(并不是Bean的整个⽣命周期完成)之后可以使用BeanPostProcessor进⾏后置处理做一些事情
注意:对象不一定是springbean,而springbean一定是个对象

1 BeanPostProcessor

postProcessBeforeInitialization 方法,执行 bean 的初始化方法前被触发;postProcessAfterInitialization 方法,执行 bean 的初始化方法后被触发。

@PostConstruct , init-method

处理执行顺序

定义⼀个类实现了BeanPostProcessor,默认是会对整个Spring容器中所有的bean进⾏处理。如果要对具体的某个bean处理,可以通过⽅法参数判断,两个类型参数分别为Object和String,第⼀个参数是每个bean的实例,第⼆个参数是每个bean的name或者id属性的值。所以我们可以通过第⼆个参数,来判断我们将要处理的具体的bean。
注意:处理是发⽣在Spring容器的实例化和依赖注⼊之后。

2 BeanFactoryPostProcessor

BeanFactory级别的处理,工厂产生之后 bean产生之前 是针对整个Bean的工厂进行处理,典型应用:PropertyPlaceholderConfigurer,

注意:调⽤ BeanFactoryPostProcessor ⽅法时,这时候bean还没有实例化,此时 bean 刚被解析成BeanDefinition对象

六 Spring 中的常见扩展点有哪些

1、ApplicationContextInitializer
initialize 方法,在 Spring 容器刷新前触发,也就是 refresh 方法前被触发。
2、BeanFactoryPostProcessor
postProcessBeanFactory 方法,在加载完 Bean 定义之后,创建 Bean 实例之前被触发,通常使用该扩展点来加载一些自己的 bean 定义。
3、BeanPostProcessor
postProcessBeforeInitialization 方法,执行 bean 的初始化方法前被触发;postProcessAfterInitialization 方法,执行 bean 的初始化方法后被触发。
4、@PostConstruct
该注解被封装在 CommonAnnotationBeanPostProcessor 中,具体触发时间是在 postProcessBeforeInitialization 方法。
5、InitializingBean
afterPropertiesSet 方法,在 bean 的属性填充之后,初始化方法(init-method)之前被触发,该方法的作用基本等同于 init-method,主要用于执行初始化相关操作。
6、ApplicationListener,事件监听器
onApplicationEvent 方法,根据事件类型触发时间不同,通常使用的 ContextRefreshedEvent 触发时间为上下文刷新完毕,通常用于 IoC 容器构建结束后处理一些自定义逻辑。
7、@PreDestroy
该注解被封装在 DestructionAwareBeanPostProcessor 中,具体触发时间是在 postProcessBeforeDestruction 方法,也就是在销毁对象之前触发。
8、DisposableBean
destroy 方法,在 bean 的销毁阶段被触发,该方法的作用基本等同于
destroy-method,主用用于执行销毁相关操作。

七 Spring中如何让两个bean按顺序加载

1、使用 @DependsOn、depends-on
2、让后加载的类依赖先加载的类

@Component
public class A {
    @Autowire
    private B b;
}


3、使用扩展点提前加载,例如:BeanFactoryPostProcessor

@Component
public class TestBean implements BeanFactoryPostProcessor {
  @Override
  public void postProcessBeanFactory(ConfigurableListableBeanFactory
          configurableListableBeanFactory) throws BeansException {
      // 加载bean
     beanFactory.getBean("a");
  }
}

要在 Spring IoC 容器构建完毕之后执行一些逻辑,怎么实现
1、比较常见的方法是使用事件监听器,实现 ApplicationListener 接口,监听 ContextRefreshedEvent 事件。
2、还有一种比较少见的方法是实现 SmartLifecycle 接口,并且 isAutoStartup 方法返回 true,则会在 finishRefresh() 方法中被触发。
两种方式都是在 finishRefresh 中被触发,SmartLifecycle在ApplicationListener之前。

八 Spring用了哪些设计模式

1)单例模式: 在Spring中定义的bean默认是单例模式。

2)工厂模式:Spring使用工厂模式通过BeanFactory、ApplicationContext创建Bean对象。

3)代理模式:Spring AOP功能的实现是通过代理模式中的动态代理实现的。

4)策略模式:Spring中资源访问接口Resource的设计是一种典型的策略模式。Resource接口是所有资源访问类所实现的接口,Resource 接口就代表资源访问策略,但具体采用哪种策略实现,Resource 接口并不理会。客户端程序只和 Resource 接口耦合,并不知道底层采用何种资源访问策略,这样客户端程序可以在不同的资源访问策略之间自由切换。

5)适配器模式:Spring AOP的增强或通知使用到了适配器模式。SpringMVC中也是用到了适配器模式适配Controller。

6)装饰器模式:Spring 中配置 DataSource 的时候,DataSource 可能是不同的数据库和数据源,项目需要连接多个数据库,这种模式让我们可以根据客户需求切换不同的数据源。

7)模板模式:Spring中jdbcTemplate、hibernateTemplate等以Template结尾的对数据库操作的类,就是用到了模板模式。

8)观察者模式:Spring事件驱动模型就是观察者模式很经典的一个应用。

九 杂记

Spring 中,有两个 id 相同的 bean,会报错吗,如果会报错,在哪个阶段报错
首先,在同一个bean里面不能出现存在id相同的两个bean,否则spring容器在启动时会报错。因为id是表示bean里面的唯一标志符号,所以spring在启动的时候回去验证id的唯一性,一旦发现重复就会报错,而错误发生在spring在对xml的文件的解析转化为BeanDefinition的一个过程中。
但是在两个不同的spring配置文件里,可以存在id相同的两个bean,ioc容器在加载bean的时候默认会把多个id相同的bean进行覆盖。
在spring3.x版本后,这个问题发生了一个变化,由于3.x版本提供了一个注解@Configuration注解去声明配置类,然后使用@Bean注解去实现bean的声明,取代了xml配置,如果实在同一个配置类里面声明多个相同的名字Bean,那么spring ioc在解析的时候只会注册第一个声明bean的实例,后面重复bean的实例就不会再注册了 。
使用@Autowired进行类型注入的时候,因为ioc容器只存在加载的第一个容器实例所以将无法加载同名异类的实例,并且报类型不匹配异常,这个错误是在spring的bean初始化之后,依赖注入阶段发生的。
@Configuration Spring 中,bean 的 class 属性指定了一个不存在的 class,会报错吗,如果会报错,在哪个阶段
是的,如果 Spring bean 的 class 属性指定了一个不存在的类,将会抛出 ClassNotFoundException 异常。这是因为 Spring 容器在实例化 bean 时需要通过反射来调用该类的构造函数,如果找不到指定的类,就无法进行实例化操作。因此,必须确保指定的类存在并且已经被正确加载到类路径中。

spring boot

一 原理


1. 加载配置文件与启动类
当 SpringBoot 项目启动时,会首先读取项目中的配置文件,主要是 application.yml 和 application.properties 文件。这些配置文件会指定项目的启动端口号、数据库连接等一些列配置信息。同时,SpringBoot也会加载启动类,这个启动类中有@SpringBootApplication注解,它标识着这个类是SpringBoot的启动类。
2. 初始化Spring容器
加载完配置文件与启动类之后,SpringBoot会通过 Spring 框架来初始化 Spring 容器,包括根据配置文件中的配置信息注册bean,创建bean实例,完成依赖注入等操作。
3. 开启自动配置功能
SpringBoot会自动扫描项目中的类,如果这些类中有@Configuration注解,SpringBoot将会读取这个类中被@Bean注解标记的方法去生成Bean实例并注入到Spring容器中。
4. 启动内嵌的Web服务器
SpringBoot内置Tomcat和Jetty等Web服务器,当SpringBoot应用程序启动时,它会根据配置文件中的信息自动创建Tomcat或Jetty等Web容器,并将Spring容器注册到Web容器中,使得SpringBoot应用程序可以直接以Web应用程序的形式运行。
5. 启动SpringBoot应用程序
最后一步是启动SpringBoot应用程序,它会根据之前的启动步骤创建好的 Spring 容器以及Web服务器,启动相应的线程进行服务处理。
总的来说,SpringBoot的启动流程相对简单,对于开发者而言,只需要关注自己的业务逻辑和所需要的依赖库,就能够快速构建出一款可运行的应用程序。

 

@EnableAutoConfiguration是利用AutoConfigurationImportSelector给容器中导入一些组件。 

描所有jar包类路径下"META-INF/spring.factories"  然后把扫描到的这些文件包装成Properties对象

从properties中获取到EnableAutoConfiguration.class类名对应的值,然后把他们添加在容器中

整个过程就是将类路径下"META-INF/spring.factories"里面配置的所有EnableAutoConfiguration的值加入到容器中

SpringBoot内置Tomcat启动流程要从main函数入手,而main函数中的run()方法实际上是调用SpringApplication的run()方法。在run()方法中,先创建一个ConfigurableApplicationContext对象,通过createApplicationContext()对象进行创建,这个对象实际上就是JavaWeb的ApplicationContext对象。然后调用refreshContext()方法,在该方法中,又调用了refresh()方法,此方法中定义了Tomcat创建流程,调用ServletWebServerApplicationContext的onRefresh()方法,在该方法中调用了createWebServer()方法,在该方法中,先获取ServletWebServerFactory,再根据工厂获取具体的webServer,此时获取的是TomcatServletWebServerFacotry这个工厂,然后在getWebServer()方法中,创建Tomcat的一些核心组件。然后调用getTomcatWebServer()方法,进行初始化Tomcat。最后调用refresh()中的finishRefresh()方法,该方法被ServletWebServerApplicationContext子类重写,在该方法中调用start()方法将Tomcat启动。
————————————————
AbstractApplicationContext--->refresh()中的onRefresh方法 ---createWebServer方法--》getwebserver

 SpringBoot配置文件——加载顺序

 

如果在不同的目录中存在多个配置文件,它的读取顺序是:
1、config/application.properties(项目根目录中config目录下)
2、config/application.yml
3、application.properties(项目根目录下)
4、application.yml
5、resources/config/application.properties(项目resources目录中config目录下)
6、resources/config/application.yml
7、resources/application.properties(项目的resources目录下)
8、resources/application.yml 

spring-cloud

最新的SpringCloud(H版&Alibaba)技术(1-4基础入门,创建项目)-CSDN博客

 springCloud和dubbo 有哪些区别?

  1. 从技术维度上,其实SpringCloud远远的超过Dubbo,Dubbo本身只是实现了服务治理
  2.  服务的调用方式Dubbo使用的是RPC远程调用,而SpringCloud使用的是 Rest API,其实更符合微服务官方的定义
  3. 服务的注册中心来看,Dubbo使用了第三方的ZooKeeper作为其底层的注册中心,实现服务的注册和发现,SpringCloud使用Spring Cloud Netflix Eureka实现注册中心,当然SpringCloud也可以使用ZooKeeper实现,但一般我们不会这样做

  4. 服务网关,Dubbo并没有本身的实现,只能通过其他第三方技术的整合,而SpringCloud有Zuul路由网关,作为路由服务器,进行消费者的请求分发 

Nacos

Nacos的服务注册表结构是怎样的?

Nacos采用了数据的分级存储模型,最外层是Namespace,用来隔离环境。然后是Group,用来对服务分组。接下来就是服务(Service)了,一个服务包含多个实例,但是可能处于不同机房,因此Service下有多个集群(Cluster),Cluster下是不同的实例(Instance)。

对应到Java代码中,Nacos采用了一个多层的Map来表示。结构为Map<String, Map<String, Service>>,其中最外层Map的key就是namespaceId,值是一个Map。内层Map的key是group拼接serviceName,值是Service对象。Service对象内部又是一个Map,key是集群名称,值是Cluster对象。而Cluster对象内部维护了Instance的集合

Nacos如何支撑数十万服务注册压力?
Nacos内部接收到注册的请求时,不会立即写数据,而是将服务注册的任务放入一个阻塞队列就立即响应给客户端。然后利用线程池读取阻塞队列中的任务,异步来完成实例更新,从而提高并发写能力。
Nacos如何避免并发读写冲突问题?
Nacos在更新实例列表时,会采用CopyOnWrite技术,首先将旧的实例列表拷贝一份,然后更新拷贝的实例列表,再用更新后的实例列表来覆盖旧的实例列表。
这样在更新的过程中,就不会对读实例列表的请求产生影响,也不会出现脏读问题了。

Eurkea自我保护机制 

用电视剧新三国中,刘备说的:“宁可天下人负我,我不负天下人”。即:Eureka服务端,进入自我保护模式,就算所有的微服务真的都出问题了,也不会里面删除它们的

默认情况下,如果Eureka Server在一定时间内(默认 90 秒,其实不止 90 秒)没有接收到某个微服务实例的心跳,Eureka Server将会移除该实例。但是当网络分区故障发生时,微服务与Eureka Server之间无法正常通信,而微服务本身是正常运行的,此时不应该移除这个微服务,所以引入了自我保护机制。

官方对于自我保护机制的定义: 自我保护模式正是一种针对网络异常波动的安全保护措施,使用自我保护模式能使 Eureka 集群更加的健壮、稳定的运行。

自我保护机制的工作机制是:如果在15分钟内超过 85% 的客户端节点都没有正常的心跳,那么Eureka就认为客户端与注册中心出现了网络故障,Eureka Server 自动进入自我保护机制,此时会出现以下几种情况:

  • 1、Eureka Server不再从注册列表中移除因为长时间没收到心跳而应该过期的服务。

  • 2、Eureka Server仍然能够接受新服务的注册和查询请求,但是不会被同步到其它节点上,保证当前节点依然可用。

  • 3、当网络稳定时,当前Eureka Server新的注册信息会被同步到其它节点中。

因此Eureka Server可以很好的应对因网络故障导致部分节点失联的情况,而不会像 ZK 那样如果有一半不可用的情况会导致整个集群不可用而变成瘫痪。

Eureka Server 自我保护机制,可以通过通过配置 eureka.server.enable-self-preservation 来 true 打开/ false 禁用 自我保护机制,默认打开状态,建议生产环境打开此配置。

Eureka和ZooKeeper都可以提供服务注册与发现的功能,请说说两个的区别

ZooKeeper保证的是CP,Eureka保证的是AP,ZooKeeper在选举期间注册服务瘫痪,虽然服务最终会恢复,但是选举期间不可用的。Eureka各个节点是平等关系,只要有一台Eureka就可以保证服务可用,而查询到的数据并不是最新的自我保护机制会导致Eureka不再从注册列表移除因长时间没收到心跳而应该过期的服务。Eureka仍然能够接受新服务的注册和查询请求,但是不会被同步到其他节点(高可用)。当网络稳定时,当前实例新的注册信息会被同步到其他节点中(最终一致性)。Eureka可以很好的应对因网络故障导致部分节点失去联系的情况,而不会像ZooKeeper一样使得整个注册系统瘫痪。
1、 ZooKeeper有Leader和Follower角色,Eureka各个节点平等
2、 ZooKeeper采用过半数存活原则,Eureka采用自我保护机制解决分区问题
3、 Eureka本质上是一个工程,而ZooKeeper只是一个进程

Eureka的服务注册与发现机制主要包括以下几个步骤:

(1)服务提供者启动时向Eureka服务器注册自己的信息,包括主机名、IP地址、端口号等;

(2)服务消费者启动时会向Eureka服务器获取自己需要调用的服务提供者的信息;

(3)Eureka服务器会将服务提供者的信息缓存起来,以便服务消费者能够快速地获取到服务提供者的信息;

(4)当服务提供者出现故障或宕机时,Eureka服务器会将该服务从服务列表中移除,并通知相关的服务消费者。

 eureka和nacos的区别

(1)、Eureka对服务提供者是每30秒一次心跳检测来检测服务健康,Nacos则把服务分为临时服务和非临时服务,对于临时服务,Nacos采取策略与Eureka相同,对于非临时服务,Nacos不会对其进行心跳检测,而是会主动调用该服务查看是否正常,若不正常会把该服务标记为不健康,不会把该服务从服务列表中去掉。
(2)、Eureka会定时向注册中心定时拉去服务,如果不主动拉去服务,注册中心不会主动推送。Nacos中注册中心会定时向消费者主动推送信息 ,这样就会保持数据的准时性。
(3)、Nacos集群默认采用AP方式,当集群中存在非临时实例时,采用CP模式;Eureka采用AP方式。
(4)、eureka是使用定时发送和服务进行联系,属于短连接;nacos使用的是netty和服务直接进行连接,属于长连接;

对于nacos服务来讲它是如何判断服务实例的状态的?

 答:nacos服务客户端(要注册到nacos的服务)启动时会每隔一段时间(默认5秒)向nacos发送心跳包,nacos注册中心15秒内没有检测到心跳包会默认认为nacos处于一种不健康的状态,30秒还没收到则认为这个服务已不可用。

 nacos自动刷新原理

其实nacos上配置变更有通知到应用,通过观察日志就可以观察出来,但是由于bean已经创建好,配置变更后不会重新创建所以对应的配置值没有变更。
@RefreshScope注解加上之后,这个bean会放进refresh scope这个bean缓存中,当配置中心发生变更后,会把变更的配置更新到spring容器的Environment中,bean缓存就会被清空,从而创建新的bean实例,所以此时@value能从Environment中获取最新的值,达到动态刷新的效果

 我们在首次启动服务的时候,其实spring已经加载了nacos的jar,此时spring是有监听器的,通过定时去获取nacos的配置是否发生了变化,因为上面说了我们是通过长轮询+主动去Pull拉模式方式从nacos服务端获取配置的,那么肯定是缓存在本地的,如果spring启动一个定时任务专门在本地进行比较就会发现value值是改变的,然后监听到了这个事件就会通过发布事件去处理实现自动刷新配置业务,只会更新@RefreshScope和@Component + @ConfigurationProperties注解下的类的,其他的不会更新的

Nacos实现配置的动态刷新的原理主要有两方面。首先,Nacos采用的是长轮询的方式,由Nacos Client向Nacos Server端发起配置更新查询的请求。所谓长轮询,就是当服务器端的配置没有任何变更的时候,这个连接会一直打开,直到服务端有配置变更或者连接超时之后才返回。其次,SpringBoot项目中使用@RefreshScope注解修饰bean,这样被@RefreshScope代理的bean可以实现配置、实例的热加载。也就是说,当配置变更时,可以在不重启应用的前提下刷新bean中相关的属性值。最后,通过Spring的ApplicationContext.publishEvent()发布事件的方式去刷新容器

Nacos经典7道面试题_nacos面试题-CSDN博客

Nginx与Ribbon的区别

Nginx是反向代理同时可以实现负载均衡,nginx拦截客户端请求采用负载均衡策略根据upstream配置进行转发,相当于请求通过nginx服务器进行转发。Ribbon是客户端负载均衡,从注册中心读取目标服务器信息,然后客户端采用轮询策略对服务直接访问,全程在客户端操作。 

Ribbon底层实现原理 

Ribbon使用discoveryClient从注册中心读取目标服务信息,对同一接口请求进行计数,使用%取余算法获取目标服务集群索引,返回获取到的目标服务信息。

针对被@LoadBalanced修饰的RestTemplate,给该RestTemplate增加LoadBalancerInterceptor拦截器,从而实现对负载均衡器的调用。负载均衡器通过服务名获取过滤后的服务列表,通过负载均衡算法获取其中某台实例,并进行调用

Ribbon如何避免调用失效实例?

1、通过IPing检测实例,如果检测到某服务实例不存在/一定时间未响应,则会从持有服务列表中及时移除

2、保留zone的统计数据,ribbon可以避免可能访问失效的zone(剔除无实例、实例故障率大于阈值

有了ribbon 为啥还要用feign

feign和openfeign的区别

1 底层都是内置了Ribbon,去调用注册中心的服务。
2 Feign是Netflix公司写的,是SpringCloud组件中的一个轻量级RESTful的HTTP服务客户端,是SpringCloud中的第一代负载均衡客户端。
3 OpenFeign是SpringCloud自己研发的,在Feign的基础上支持了Spring MVC的注解,如@RequestMapping等等。是SpringCloud中的第二代负载均衡客户端。
4 Feign本身不支持Spring MVC的注解,使用Feign的注解定义接口,调用这个接口,就可以调用服务注册中心的服务
5 OpenFeign的@FeignClient可以解析SpringMVC的@RequestMapping注解下的接口,并通过动态代理的方式产生实现类,实现类中做负载均衡并调用其他服务。
1 Feign是Spring Cloud组件中的一个轻量级RESTful的HTTP服务客户端,
2 Feign的使用方式是:使用Feign的注解定义接口,调用这个接口,就可以调用服务注册中心的服务。
3 Feign是在2019就已经不再更新了,通过maven网站就可以看出来,随之取代的是OpenFeign,从名字上就可以知道,它是Feign的升级版。

openFeign

OpenFeign相关问题及答案(2024)_openfeign面试题-CSDN博客

Hystrix

Hystrix相关问题及答案(2024)-CSDN博客

面试官:请解释一下Hystrix的原理及其如何使用的? - 知乎

zuul

微服务网关Zuul和Gateway的区别_zuul和gateway的区别及原理-CSDN博客

为什么要用spring-cloud-gateway,不用zuul_为什么springcloud不支持zuul-CSDN博客

springcloudgateway

https://www.cnblogs.com/dingpeng9055/p/16391333.html

 Nacos的AP模式和CP模式切换

(1)切换为CP模式

curl -X PUT "http://localhost:8848/nacos/v1/ns/operator/switches?entry=serverMode&value=CP"

2)切换为AP模式

curl -X PUT "http://localhost:8848/nacos/v1/ns/operator/switches?entry=serverMode&value=AP" 

CP:我们服务可以不能用,但必须要保证数据的一致性。
AP:数据可以短暂不一致,但最终是需要一致的,无论如何都要保证服务的可用。
只取舍:有在CP和AP选择一个平衡点,大多数都是选择AP模式。 

这个不能随便切,建议保持默认的AP即可。

分布式系统中,数据的一致性对业务有非常高的要求,例如银行转账、支付等场景,那么可以选择使用CP模式。在CP模式下,当发生网络分区或故障时,为了保证数据一致性,Nacos会对服务进行自动隔离和恢复。但是,这样导致部分服务不可用,性能收到影响

如果对于某些服务之间调用来说,每次请求都需要相应来说,可以选择AP模式,数据可以短暂不一致,但最终是需要一致的,无论如何都要保证服务的可用。

sentinel(豪猪哥的阿里版)

官方文档:https://github.com/alibaba/spring-cloud-alibaba/wiki/Sentinel

Sentinel面试 17 问_sentinel面试题-CSDN博客

sentinel规则持久--保存到nacos

sentinel默认限流算法

sentinel限流原理详解

Hystrix和Sentinel以及Sentinel原理剖析 - 知乎

seata 

 Seata 是什么? | Apache Seata

mybatis 

                                                 工作流程串联起来,简单总结一下

  1.  读取 MyBatis 配置文件——mybatis-config.xml 、加载映射文件——映射文件即 SQL 映射文件,文件中配置了操作数据库的 SQL 语句。最后生成一个配置对象 。配置⽂件内容解析封装到Configuration,通过XMLConfigBuilder将sql的配置信息加载成为⼀个mappedstatement 对象,存储在内存之中
  2. 构造会话工厂:通过 MyBatis 的环境等配置信息构建会话工厂 SqlSessionFactory
    (SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
    )
  3. 创建会话对象:由会话工厂创建 SqlSession(SqlSessionFactory.openSession()) (DefaultSqlSession里面有Configuration和Executor)对象,该对象中包含了执行 SQL 语句的所有方法。
  4. Executor 执行器:MyBatis 底层定义了一个 Executor 接口来操作数据库,它将根据 SqlSession 传递的参数动态地生成需要执行的 SQL 语句,同时负责查询缓存的维护。
  5. StatementHandler:数据库会话器,串联起参数映射的处理和运行结果映射的处理。
  6. 参数处理:对输入参数的类型进行处理,并预编译。
  7. 结果处理:对返回结果的类型进行处理,根据对象映射规则,返回相应的对象。

Mapper代理⽅式

Mybatis源码解析之mapper接口的代理模式详解_java_脚本之家

Mybatis源码解析(八):Mapper代理原理_mybatismapperproxy-CSDN博客

一 Mybatis缓存 

PerpetualCache这个类是 mybatis默认实现缓存功能的类

我们不写type就使⽤mybatis默认的缓存,也可以去实现Cache接⼝ 来⾃定义缓存

  • 一级缓存(本地缓存 就是一个HashMap)
    sqlSession级别的缓存。(相当于一个方法内的缓存)key =statmentID+ offset + limit + sql + param value + environment id。
  • 一级缓存时执行commit,close,增删改等操作,就会清空当前的一级缓存;当对SqlSession执行更新操作(update、delete、insert)后并执行commit时,不仅清空其自身的一级缓存(执行更新操作的效果),也清空二级缓存(执行commit()的效果)。
  • 二级缓存(全局缓存 就是一个HashMap)
    基于namespace名称空间级别的缓存.即一个mapper.xml对应一个缓存
  • MyBatis的二级缓存不适应用于映射文件中存在多表查询的情况,如果多个映射文件对应的Sql操作都使用的是同一块缓存,那么缓存的粒度就变粗了,多个Mapper namespace下的所有操作都会对缓存使用造成影响。
  • 分布式环境下,必然会出现读取到错误数据,所以不推荐使用。
  • 假设现在有 ItemMapper 以及 XxxMapper,在 XxxMapper 中做了表关联查询,且做了二级缓存。此时在 ItemMapper 中将 item 信息给删了,由于不同 namespace 下的操作互不影响,XxxMapper 的二级缓存不会变,那之后再次通过 XxxMapper 查询的数据就不对了,非常危险

 开启二级缓存方式

1、在配置文件中 开启二级缓存的总开关

<setting name="cacheEnabled" value="true" />

2、 在mapper映射文件中开启二级缓存

<cache eviction="FIFO" flushInterval="60000" size="512" 
readOnly="true"/>

 缓存的顺序:二级缓存--》一级缓存--》数据库

Mybatis 是如何进行分页的?分页插件的原理是什么?

  • Mybatis 使用 RowBounds 对象进行分页,也可以直接编写 sql 实现分页,也可以使用Mybatis 的分页插件。
  • 分页插件的原理:实现 Mybatis 提供的接口,实现自定义插件,在插件的拦截方法内拦截待执行的 sql,然后重写 sql。
  • 举例:select * from student,拦截 sql 后重写为:select t.* from (select * from student)tlimit 0,10

Mybatis 是否支持延迟加载?如果支持,它的实现原理是什么? 

  • Mybatis 仅支持 association 关联对象和 collection 关联集合对象的延迟加载,association指的就是一对一,collection 指的就是一对多查询。在 Mybatis 配置文件中,可以配置是否启用延迟加载 lazyLoadingEnabled=true|false。
  • 它的原理是,使用 CGLIB 创建目标对象的代理对象,当调用目标方法时,进入拦截器方法,比如调用 a.getB().getName(),拦截器 invoke()方法发现 a.getB()是 null 值,那么就会单独发送事先保存好的查询关联 B 对象的 sql,把 B 查询上来,然后调用 a.setB(b),于是 a 的对象 b 属性就有值了,接着完成 a.getB().getName()方法的调用。这就是延迟加载的基本原理。

 Mybatis插件

(Executor、StatementHandler、ParameterHandler、ResultSetHandler)处提供了简单易⽤的插 件扩展机制。Mybatis对持久层的操作就是借助于四⼤核⼼对象。MyBatis⽀持⽤插件对四⼤核⼼对象进 ⾏拦截,对mybatis来说插件就是拦截器,⽤来增强核⼼对象的功能,增强功能本质上是借助于底层的 动态代理实现的,换句话说,MyBatis中的四⼤对象都是代理对象

MyBatis所允许拦截的⽅法如下:

  1. 执⾏器Executor (update、query、commit、rollback等⽅法);
  2. SQL语法构建器StatementHandler (prepare、parameterize、batch、updates query等⽅ 法);
  3. 参数处理器ParameterHandler (getParameterObject、setParameters⽅法);
  4. 结果集处理器ResultSetHandler (handleResultSets、handleOutputParameters等⽅法);

 在四⼤对象创建的时候
1、每个创建出来的对象不是直接返回的,⽽是interceptorChain.pluginAll(parameterHandler);
2、获取到所有的Interceptor (拦截器)(插件需要实现的接⼝);调⽤ interceptor.plugin(target);返回 target 包装后的对象
3、插件机制,我们可以使⽤插件为⽬标对象创建⼀个代理对象; AOP (⾯向切⾯)我们的插件可 以为四⼤对象创建出代理对象,代理对象就可以拦截到四⼤对象的每⼀个执⾏;

@Intercepts({
@Signature(
type = Executor.class,
method = "query",
args={MappedStatement.class,Object.class,RowBounds.class,ResultHandler.class}
)
})
@Component
public class ExeunplePlugin implements Interceptor {
    //省略逻辑
}

这样MyBatis在启动时可以加载插件,并保存插件实例到相关对象(InterceptorChain,拦截器链) 中。待准备⼯作做完后, MyBatis处于就绪状态。我们在执⾏SQL时,需要先通过DefaultSqlSessionFactory 创建 SqlSession。
Executor 实例会在创建 SqlSession 的过程中被创建, Executor实例创建完毕后, MyBatis会通过JDK动态代理为实例⽣成代理类。这样,插件逻辑即可在 Executor相关⽅法被调⽤前执⾏。

Plugin实现了 InvocationHandler接⼝,因此它的invoke⽅法会拦截所有的⽅法调⽤。 invoke⽅法会 对所拦截的⽅
法进⾏检测,以决定是否执⾏插件逻辑。该⽅法的逻辑如下

invoke⽅法的代码⽐较少,逻辑不难理解。⾸先,invoke⽅法会检测被拦截⽅法是否配置在插件的 @Signature注解
中,若是,则执⾏插件逻辑,否则执⾏被拦截⽅法

// -Plugin
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		try {
			/*
			 *获取被拦截⽅法列表,⽐如:
			 * signatureMap.get(Executor.class), 可能返回 [query, update, commit]
			 */
			Set<Method> methods = signatureMap.get(method.getDeclaringClass());
            //检测⽅法列表是否包含被拦截的⽅法
			if (methods != null && methods.contains(method)) {
                //执⾏插件逻辑
				return interceptor.intercept(new Invocation(target, method, args));
                //执⾏被拦截的⽅法
				return method.invoke(target, args);
			} catch(Exception e){
			}
		}


 

 架构原理

 设计模式

【分享】从Mybatis源码中,学习到的10种设计模式 - 知乎

Redis      

全网最硬核 Redis 高频面试题解析(2021年最新版)_51CTO博客_redis面试题高级   

Redis 在项目中的使用场景

一:缓存——热数据

热点数据(经常会被查询,但是不经常被修改或者删除的数据),首选是使用redis缓存,毕竟强大到冒泡的QPS和极强的稳定性不是所有类似工具都有的,而且相比于memcached还提供了丰富的数据类型可以使用,另外,内存中的数据也提供了AOF和RDB等持久化机制可以选择,要冷、热的还是忽冷忽热的都可选。
结合具体应用需要注意一下:很多人用spring的AOP来构建redis缓存的自动生产和清除,过程可能如下:
Select 数据库前查询redis,有的话使用redis数据,放弃select 数据库,没有的话,select 数据库,然后将数据插入redis
update或者delete数据库钱,查询redis是否存在该数据,存在的话先删除redis中数据,然后再update或者delete数据库中的数据
上面这种操作,如果并发量很小的情况下基本没问题,但是高并发的情况请注意下面场景:
为了update先删掉了redis中的该数据,这时候另一个线程执行查询,发现redis中没有,瞬间执行了查询SQL,并且插入到redis中一条数据,回到刚才那个update语句,这个悲催的线程压根不知道刚才那个该死的select线程犯了一个弥天大错!于是这个redis中的错误数据就永远的存在了下去,直到下一个update或者delete。
二:计数器
诸如统计点击数等应用。由于单线程,可以避免并发问题,保证不会出错,而且100%毫秒级性能!爽。
命令:INCRBY
当然爽完了,别忘记持久化,毕竟是redis只是存了内存!
三:队列
相当于消息系统,ActiveMQ,RocketMQ等工具类似,但是个人觉得简单用一下还行,如果对于数据一致性要求高的话还是用RocketMQ等专业系统。
由于redis把数据添加到队列是返回添加元素在队列的第几位,所以可以做判断用户是第几个访问这种业务
队列不仅可以把并发请求变成串行,并且还可以做队列或者栈使用
四:位操作(大数据处理)
用于数据量上亿的场景下,例如几亿用户系统的签到,去重登录次数统计,某用户是否在线状态等等。
想想一下腾讯10亿用户,要几个毫秒内查询到某个用户是否在线,你能怎么做?千万别说给每个用户建立一个key,然后挨个记(你可以算一下需要的内存会很恐怖,而且这种类似的需求很多,腾讯光这个得多花多少钱。。)好吧。这里要用到位操作——使用setbit、getbit、bitcount命令。
原理是:
redis内构建一个足够长的数组,每个数组元素只能是0和1两个值,然后这个数组的下标index用来表示我们上面例子里面的用户id(必须是数字哈),那么很显然,这个几亿长的大数组就能通过下标和元素值(0和1)来构建一个记忆系统,上面我说的几个场景也就能够实现。用到的命令是:setbit、getbit、bitcount
五:分布式锁与单线程机制
验证前端的重复请求(可以自由扩展类似情况),可以通过redis进行过滤:每次请求将request Ip、参数、接口等hash作为key存储redis(幂等性请求),设置多长时间有效期,然后下次请求过来的时候先在redis中检索有没有这个key,进而验证是不是一定时间内过来的重复提交
秒杀系统,基于redis是单线程特征,防止出现数据库“爆破”
全局增量ID生成,类似“秒杀”
六:最新列表
例如新闻列表页面最新的新闻列表,如果总数量很大的情况下,尽量不要使用select a from A limit 10这种low货,尝试redis的 LPUSH命令构建List,一个个顺序都塞进去就可以啦。不过万一内存清掉了咋办?也简单,查询不到存储key的话,用mysql查询并且初始化一个List到redis中就好了。
七:排行榜
谁得分高谁排名往上。命令:ZADD(有续集,sorted set)
 

Redis 怎么保证高可用

  1. 主从复制(Master-Slave Replication): Redis支持主从复制机制,其中一个Redis实例(主节点)负责写操作,而其他实例(从节点)复制主节点的数据。如果主节点发生故障,可以快速切换到从节点来提供读写服务,从而实现故障转移和高可用性。
  2. Sentinel哨兵: Redis Sentinel是一个监控和自动故障恢复系统,它能够监视Redis主从节点的健康状况。当主节点发生故障时,Sentinel可以自动选择一个从节点升级为新的主节点,确保系统的持续可用性。Sentinel还可以配置多个哨兵以提高可用性。
  3. Cluster集群: Redis Cluster是一种分布式Redis解决方案,它可以将数据分片存储在多个节点上,并自动管理节点间的数据分布和故障转移。Redis Cluster提供了高可用性和扩展性,允许在集群中添加或删除节点而不会影响整个系统的可用性。
  4. 持久化: Redis提供了多种持久化方式,如RDB快照和AOF日志文件。通过将数据写入磁盘,Redis可以在重新启动时恢复数据,从而减少数据丢失的风险。
  5. 数据备份: 定期对Redis数据库进行备份是一种常见的高可用性策略。备份数据可以在主节点故障时用于恢复。这可以通过使用Redis自带的 BGSAVE命令或外部工具来实现。
  6. 监控和警报: 建立有效的监控和警报系统可以及时发现问题并采取行动。监控Redis的关键指标,如内存使用、连接数和响应时间,可以帮助管理员在问题发生时快速响应。
  7. 故障恢复策略: 为了应对不同类型的故障,Redis需要定义故障恢复策略。这包括主从切换、故障节点恢复和数据同步等策略。

Redis 的选举流程

集群选举:Redis学习(二)之 Redis Cluster集群_redis集群选举机制-CSDN博客

Redis 和 Memcache 的区别

  1. redis 支持5种数据类型,memcache只支持一种字符串类型
  2. redis 支持两种持久化功能,rdb 和 aof , memcache 不支持持久化功能
  3. memcache 多线程,需要依赖libevent这样的系统类 ,redis 单线程(Redis 6.0 多线程),Redis不需要依赖于操作系统中的类库

Redis 的集群模式

  • redis有三种集群模式,其中主从是最常见的模式。
  • Sentinel 哨兵模式是为了弥补主从复制集群中主机宕机后,主备切换的复杂性而演变出来的。哨兵顾名思义,就是用来监控的,主要作用就是监控主从集群,自动切换主备,完成集群故障转移。
  • cluster 模式是redis官方提供的集群模式,使用了Sharding 技术,不仅实现了高可用、读写分离、也实现了真正的分布式存储

https://www.cnblogs.com/iwenwen/p/14286269.html

Redis的三种集群方式详解_redis集群-CSDN博客

Redis 集群要增加分片,槽的迁移怎么保证无损

Redis 分布式锁的实现

Redisson 实现分布式锁原理解析_redissonlock实现可重复原理-CSDN博客 

https://www.cnblogs.com/linyue09/p/13864288.html

Redis 删除过期键的策略

Redis 的内存淘汰策略

Redis的数据过期清除策略 与 内存淘汰策略_张维鹏-华为云开发者联盟

Redis 的 Hash 对象底层结构

Redis数据结构:Hash类型全面解析_redis hash-CSDN博客

Redis 中 Hash 对象的扩容流程

redis 解决冲突的方法是使用链地址法,另外当容量不足的时候,则使用Rehash 进行扩容。
Rehash:
给哈希表 2 分配更大的空间,
例如是当前哈希表 1 大小的两倍;
把哈希表 1 中的数据重新映射并拷贝到哈希表 2 中;
释放哈希表 1 的空间。
渐进式rehash则是不一次性拷贝,当访问到某个数据时,再进行拷贝。

redis中hash扩容过程-腾讯云开发者社区-腾讯云

Redis的哈希表是如何扩容的?_redis hash扩容-CSDN博客

redis中hash扩容过程_redis hash扩容-CSDN博客

Redis 的 Hash 对象的扩容流程在数据量大的时候会有什么问题吗

1)扩容期开始时,会先给 ht[1] 申请空间,所以在整个扩容期间,会同时存在 ht[0] 和 ht[1],会占用额外的空间。
2)扩容期间同时存在 ht[0] 和 ht[1],查找、删除、更新等操作有概率需要操作两张表,耗时会增加。
3)redis 在内存使用接近 maxmemory 并且有设置驱逐策略的情况下,出现 rehash 会使得内存占用超过 maxmemory,触发驱逐淘汰操作,导致 master/slave 均有有大量的 key 被驱逐淘汰,从而出现 master/slave 主从不一致。

Redis 的持久化机制有哪几种

RDB 和 AOF

RDB 和 AOF 的实现原理、优缺点

一、RDB模式
1.1 工作原理
RDB(Redis DataBase):基于时间的快照,其默认只保留当前最新的一次快照,特点是执行速度比较快,缺点是可
能会丢失从上次快照到当前时间点之间未做快照的数据。
RDB bgsave 实现快照的具体过程:

Redis从master主进程先fork出一个子进程,使用写时复制机制,将内存的数据保存为一个临时文件,比如:tmp.rdb。当数据保存完成之后再将上一次保存的RDB文件替换掉,然后关闭子进程,这样可以保证每一次做RDB快照保存的数据都是完整的。

因为直接替换RDB文件的时候,可能会出现突然断电等问题,而导致RDB文件还没有保存完整就因为突然关机停止保存,而导致数据丢失的情况。后续可以手动将每次生成的RDB文件进行备份,这样可以最大化保存历史数据。

1.2 RDB优点
RDB是基于时间的快照,即保存了某个时间点的数据。很适合备份,可以根据自身的业务需求灵活调整备份频率。并且此文件格式也支持有不少第三方工具可以进行后续的数据分析。即使遇上问题,也可以随时将数据集还原到不同的版本。
RDB可以最大化Redis的性能,父进程在保存RDB文件时唯一要做的就是fork出一个子进程,然后这个子进程就会处理接下来的所有保存工作,父进程无须执行任何磁盘工/0操作。
RDB在大量数据,比如几个G的数据,恢复的速度比AOF的快
1.3 RDB缺点
RDB本身的备份机制决定了不能实时保存数据,因此一般会超过5分钟以上才保存一次RDB文件。在这种情况下,一旦发生故障停机,就可能会丢失好几分钟的数据。

当数据量非常大的时候,从父进程fork子进程进行保存至RDB文件时需要一点时间,可能是毫秒或者秒,取决于磁盘IO性能。在数据集比较庞大时,fork()可能会非常耗时,造成服务器在一定时间内停止处理客户端﹔如果数据集非常巨大,并且CPU时间非常紧张的话,那么这种停止时间甚至可能会长达整整一秒或更久。虽然 AOF重写也需要进行fork(),但无论AOF重写的执行间隔有多长,数据的持久性都不会有任何损失。

二、AOF模式
2.1 工作原理
AOF:AppendOnylFile:按照操作顺序依次将操作追加到指定的日志文件末尾。

AOF和RDB一样使用了写时复制机制,AOF默认为每秒钟 fsync一次,即将执行的命令保存到AOF文件当中,这样即使redis服务器发生故障的话最多只丢失1秒钟之内的数据,也可以设置不同的fsync策略always,即设置每次执行命令的时候执行fsync,fsync会在后台执行线程,所以主线程可以继续处理用户的正常请求而不受到写入AOF文件的I/O影响。。

2.2 AOF优点
数据安全性相对较高,根据所使用的fsync策略(fsync是同步内存中redis所有已经修改的文件到存
储设备),默认是appendfsync everysec,即每秒执行一次 fsync,在这种配置下,Redis 仍然可以保
持良好的性能,并且就算发生故障停机,也最多只会丢失一秒钟的数据( fsync会在后台线程执行,
所以主线程可以继续努力地处理命令请求)

由于该机制对日志文件的写入操作采用的是append模式,因此在写入过程中不需要seek, 即使出现
宕机现象,也不会破坏日志文件中已经存在的内容。然而如果本次操作只是写入了一半数据就出现
了系统崩溃问题,不用担心,在Redis下一次启动之前,可以通过 redis-check-aof 工具来解决数据
一致性的问题。

Redis可以在 AOF文件体积变得过大时,自动地在后台对AOF进行重写,重写后的新AOF文件包含了
恢复当前数据集所需的最小命令集合。整个重写操作是绝对安全的,因为Redis在创建新 AOF文件
的过程中,append模式不断的将修改数据追加到现有的 AOF文件里面,即使重写过程中发生停
机,现有的 AOF文件也不会丢失。而一旦新AOF文件创建完毕,Redis就会从旧AOF文件切换到新
AOF文件,并开始对新AOF文件进行追加操作。

AOF包含一个格式清晰、易于理解的日志文件用于记录所有的修改操作。事实上,也可以通过该文
件完成数据的重建。

AOF文件有序地保存了对数据库执行的所有写入操作,这些写入操作以Redis协议的格式保存,因
此 AOF文件的内容非常容易被人读懂,对文件进行分析(parse)也很轻松。导出(export)AOF文件
也非常简单:举个例子,如果不小心执行了FLUSHALL.命令,但只要AOF文件未被重写,那么只要停
止服务器,移除 AOF文件末尾的FLUSHAL命令,并重启Redis ,就可以将数据集恢复到FLUSHALL执
行之前的状态

2.3 AOF缺点
即使有些操作是重复的也会全部记录,AOF 的文件大小要大于 RDB 格式的文件
AOF 在恢复大数据集时的速度比 RDB 的恢复速度要慢
根据fsync策略不同,AOF速度可能会慢于RDB
bug 出现的可能性更多

Redis持久化方式之RDB和AOF的原理及优缺点_Redis_脚本之家

Redis持久化:RDB和AOF_redis aof rdb-CSDN博客

AOF 重写的过程

AOF重写过程详解-CSDN博客

Redis AOF 文件重写流程 - 码农教程

哨兵模式的原理

面试官:为什么 Redis 要有哨兵? - 知乎

哨兵模式原理_Redis集群的三种模式原理详解-CSDN博客

使用缓存时,先操作数据库还是先操作缓存

https://www.cnblogs.com/fanguangdexiaoyuer/p/9545181.html

先更新缓存还是先更新数据库_先更新缓存再更新数据库-CSDN博客

为什么是让缓存失效,而不是更新缓存

https://www.cnblogs.com/p6545244/p/16017543.html

缓存穿透、缓存击穿、缓存雪崩

更新缓存的几种设计模式

几种缓存更新的设计方法,值得一看 | 码农家园

MySQL和Redis操作的原子性 mysql与redis_colddawn的技术博客_51CTO博客

Kafka

为什么使用 Kafka

基于发布与订阅的消息系统那么多,为什么Kafka会是一个更好的选择呢?

 1. 削峰 诸如双十一零点时淘宝的数据量陡增,服务器会承担极大的压力
我们不能以在数据量最大的时候所需要的资源数来配置资源,那样除了在某一段时间或某一刻数据量非常大,其他时间数据量都很小,会浪费很多资源

 2. 异步处理 为了实现快速响应用户,我们可以先完成必须的工作,而不必等待所有的工作做完
通常像通过邮件发送用户手册的任务可以稍后再做

 3. 解耦 多个系统为了减少相互依赖性,避免一个系统的改动引起其他系统的改动,可以使用消息中间件来达到目的
那么为什么使用消息中间件可以起到解耦的作用呢?

1 多个生产者
Kafka可以无缝地支持多个生产者,不管客户端在使用单个主题还是多个主题。所以它很适合用来从多个前端系统收集数据,并以统一的格式对外提供数据。例如, 一个包含了多个微服务的网站,可以为页面视图创建一个单独的主题,所有服务都以相同的消息格式向该主题写入数据。消费者应用程序会获得统一的页面视图,而无需协调来自不同生产者的数据流。
2 多个消费者
除了支持多个生产者外,Kafka也支持多个消费者从一个单独的消息流上读取数据,而且消费者之间直不影响。这与其他队列系统不同,其他队列系统的消息一旦被一个客户端读取,其他客户端就无法再读取它。另外,多个消费者可以组成一个群组,它们共享一个消息流,并保证整个群组对每个给定的消息中处理一次。
3 基于磁盘的数据存储
Kafka不仅支持多个消费者,还允许消费者非实时地读取消息,这要归功于Kafka 的数据保留特性。消息被提交到磁盘,根据设置的保留规则进行保存。每个主题可以设置单独的保留规则,以便满足不同消费者的需求,各个主题可以保留不同数量的消息。消费者可能会因为处理速度慢或突发的流量高峰导致无陆及时读取消息,而持久化数据可以保证数据不会丢失。消费者可以在进行应用程序维护时离线一小段时间,而无需担心消息丢失或堵塞在生产者端。消费者可以被关闭,但消息会继续保留在Kafka里。消费者可以从上次中断的地方继续处理消息。
4 伸缩性
为了能够轻松处理大量数据, Kafka从一开始就被设计成一个具有灵活伸缩性的系统。用户在开发阶段可以先使用单个broker ,再扩展到包含3个broker 的小型开发集群,然后随着数据量不断增长,部署到生产环境的集群可能包含上百个broker。对在线集群进行扩展丝毫不影响整体系统的可用性。也就是说, 一个包含多个broker的集群,即使个别broker失效,仍然可以持续地为客户提供服务。要提高集群的容错能力,需要配置较高的复制系数。
5 高性能
上面提到的所有特性,让Kafka成为了一个高性能的发布与订阅消息系统。通过横向扩展生产者、消费者和broker, Kafka可以轻松处理巨大的消息流。在处理大量数据的同时,它还能保证亚秒级的消息延迟。

介绍下 Kafka 的各个组件

Kafka原理解析(一):基本组件介绍_kafka总体架构中各个组件含义功能工作原理-CSDN博客

如何保证写入 Kafka 的数据不丢失

kafka如何保证不丢失数据_kafka如何保证数据不缺失数据-CSDN博客

Kafka怎么保证数据不丢失,不重复_kafka如何保证不重复消费又不丢失数据-CSDN博客

kafka如何保障数据不丢失_kafaka如何保证数据不丢失-CSDN博客

如何保证从 Kafka 消费的数据不丢失

Kafka怎么保证数据不丢失,不重复_kafka如何保证不重复消费又不丢失数据-CSDN博客

Kafka 为什么性能这么高

Kafka高性能原因_kafka性能高的原因-CSDN博客

零拷贝技术使用哪个方法实现

kafka 零拷贝如何实现的(FileChannel)_kafka零拷贝是怎么实现的-CSDN博客

Java 中也有类似的零拷贝技术,是哪个方法

Java 两种zero-copy零拷贝技术mmap和sendfile的介绍_sendfile和mmap的比较-CSDN博客

音视频开发技术(26)数据零拷贝实现的几种方式 - 知乎

Kafka 怎么保证消息的顺序消费

Kafka如何保证消息消费的顺序性_kafak消费端乱序-CSDN博客

28. 业务上需要顺序消费,怎么保证时序性 - 简书

kafka怎么保证消息顺序?_kafka消息的顺序性-CSDN博客

Kafka 怎么避免重复消费

如何避免Kafka的重复消费_kafka面试避免重复消费-CSDN博客

kafka 如何保证不重复消费又不丢失数据? - 知乎

什么是 HighWatermark 和 LEO

https://www.cnblogs.com/yoke/p/11486196.html

【kafka】关于 kafka LEO(Log End Offerset) 和 HW(High Watermark)概念解析 - 简书

什么是 ISR,为什么需要引入 ISR

【Java面试】什么是 ISR,为什么需要引入 ISR - 知乎

什么是ISR,为什么要引入ISR?_生产者isr指的是-CSDN博客

在Kafka中,保证数据不丢失主要通过以下几种方式实现:

  1. 多分区多副本:Kafka通过「多分区多副本」的方式来最大限度的保证数据不丢失。每个Partition都至少得有1个Follower在ISR列表里,跟上了Leader的数据同步。每次写入数据的时候,都要求至少写入Partition Leader成功,同时还有至少一个ISR里的Follower也写入成功,才算这个写入是成功了。

  2. 持久化:Kafka将所有消息持久化到磁盘上,以防止数据丢失。每个消息都会被追加到日志文件中,并且写入操作返回。

  3. 异步批量刷盘:Kafka先通过PageCache将数据存储起来,然后进行异步刷盘。虽然没有提供「同步刷盘」策略,但这种方式依然可以在一定程度上保证数据的完整性。

  4. 参数配置:可以通过配置一些参数来进一步保证数据的不丢失。比如acks参数可以用来控制生产者发送消息后的确认机制,以此来决定是否需要重新发送数据。

 Zookeeper

Zookeeper 的使用场景

ZooKeeper使用场景总结_zookeeper应用场景-CSDN博客

Zookeeper 怎么实现分布式锁

Zookeeper 分布式锁 (图解+秒懂+史上最全) - 知乎

基于Zookeeper实现分布式锁-CSDN博客

Zookeeper 怎么保证数据的一致性

https://www.cnblogs.com/tianze21334/p/14557567.html

Zookeeper 是如何保证数据一致性的_zookeeper 主从怎么保证一致性-CSDN博客

ZAB 协议的原理

浅谈Zookeeper【二】ZAB协议原理_zk的zab协议原理-CSDN博客

Zookeeper 遵循 CAP 中的哪些

ZooKeeper与CAP_zookeeper满足cap哪两个-CSDN博客

Zookeeper 和 Eureka 的区别

(1)Eureka取CAP的AP,注重可用性,Zookeeper取CAP的CP注重一致性。
(2)Zookeeper在选举期间注册服务瘫痪,虽然服务最终会恢复,但选举期间不可用。
(3)eureka的自我保护机制,会导致一个结果就是不会再从注册列表移除因长时间没收到心跳而过期的服务。依然能接受新服务的注册和查询请求,但不会被同步到其他节点。不会服务瘫痪。
(4)Zookeeper有Leader和Follower角色,Eureka各个节点平等。
(5)Zookeeper采用过半数存活原则, reka采用自我保护机制解决分区 。
(6)eureka本质是一个工程,Zookeeper只是一个进程。

Zookeeper 的 Leader 选举

https://www.cnblogs.com/fxh0707/p/17204843.html

Zookeeper的Leader选举_zookeeper选举过程-CSDN博客

Observer 的作用

三分钟明白zookeeper集群中的三种角色Leader、Follower和observer_zookeeper集群中的角色分为三种,分别是leader、follower、observer-CSDN博客

Zookeeper的观察者Observer_zookeeper的observer作用-CSDN博客

https://www.cnblogs.com/zh-dream/p/14497874.html

Leader 发送了 commit 消息,但是所有的 follower 都没有收到这条消息,Leader 就挂了,后续会怎么处理

面试官:zookeeper集群的leader挂了怎么办_zookeeper leader挂了-CSDN博客

 raft协议,leader在commit了一条日志后,立刻挂了,那其他节点如何处理这条日志? - 知乎

分布式

CAP 理论

BASE 理论

分布式事务 2PC 和 TCC 的原理

TCC 在 cancel 阶段如果出现失败怎么处理

Paxos 算法

Paxos——分布式一致性算法-腾讯云开发者社区-腾讯云

https://www.cnblogs.com/liuyi6/p/10703480.html

Raft 算法

RAFT算法详解-腾讯云开发者社区-腾讯云

二十张图带你一次性学懂Raft算法_replicated, redundant, and fault-tolerant (raft)-CSDN博客

Logo

欢迎加入西安开发者社区!我们致力于为西安地区的开发者提供学习、合作和成长的机会。参与我们的活动,与专家分享最新技术趋势,解决挑战,探索创新。加入我们,共同打造技术社区!

更多推荐