导图社区 常见面试题
以思维导图的形式,详细概述了一系列与软件开发和面试相关的常见问题和概念。覆盖了从基础知识(如自动装配、数据库操作)到高级概念(如微服务架构、并发控制)的广泛内容,旨在帮助面试者准备面试并深入理解相关技术领域。
编辑于2024-08-15 13:30:18常见面试题
springboot
自动装配的原理
具体工作原理
自动装配实现的原理: 1、创建springapplication应用对象 当启动springboot应用程序的时候,会先创建SpringApplication的对象,在对象的构造方法中会进行某些参数的初始化工作(大量的参数初始化),最主要的是判断当前应用程序的类型(是servlet类型的)以及初始化器和监听器,在这个过程中会加载整个应用程序中的spring.factories文件(META-INF/spring.factories),将文件的内容放到SpringFactoriesLoader对象的catch变量中,方便后续获取。 2、执行run方法概述 SpringApplication对象创建完成之后,开始执行run方法,来完成整个启动,启动过程中最主要的有两个方法,第一个叫做prepareContext,第二个叫做refreshContext,在这两个关键步骤中完整了自动装配的核心功能,前面的处理逻辑包含了上下文对象的创建,banner的打印,异常报告期的准备等各个准备工作,方便后续来进行调用。后者就是根据beanDefinition开始创建出对象 2.1、run方法里的preparecontext方法概述 在prepareContext方法中主要完成的是对上下文对象的初始化操作,包括了属性值的设置,比如环境对象,在整个过程中有一个非常重要的方法,叫做load,load主要完成一件事,将当前启动类做为一个beanDefinition注册到registry中,方便后续在进行BeanFactoryPostProcessor调用执行的时候,找到对应的主类,来完成@SpringBootApplicaiton,@EnableAutoConfiguration等注解的解析工作 2.2、run方法里的refreshcontext方法概述 在refreshContext方法中会进行整个容器刷新过程,会调用中spring中的refresh方法,refresh中有13个非常关键的方法,来完成整个spring应用程序的启动,在自动装配过程中,会调用Spring的 invokeBeanFactoryPostProcessor方法,在此方法中主要是对ConfigurationClassPostProcessor类的处理,(这是BeanFactoryPostProcessor的子类也是BeanDefinitionRegistryPostProcessor BDRPP的子类) 2.3bean工厂后置处理器开始工作概述 在调用的时候会先调用BDRPP中的postProcessBeanDefinitionRegistry方法, 然后调用postProcessBeanFactory方法 2.3.1调用BDRPP中的postProcessBeanDefinitionRegistry方法 在执行postProcessheanDefinitionRegistry的时候回解析处理各种注解,包含@PropertySource.@ComponentScan,@ComponentScans,@Bean.@Import等注解,最主要的是@Import注解的解析 2.3.1.1解析@Import注解 在解析@Import注解的时候,会有一个getlmports的方法,从主类开始递归解析注解,把所有包含@Import的注解都解析到,然后在processImport方法中对Import的类进行分类,此处主要识别AutoConfigurationlmportSelect归属于ImportSelect的子类,然后在后续过程中调用其selectImport方法,这个方法会从springFactoriesLoader的catch中获取key是EnableAutoConfigration的对应的value值,根据@Conditional注解过滤掉不符合条件,也就是不需要创建的类,对剩下的类进行实例化实现自动装配
加载配置类方式
@compnentScan注解
需要指定包的名称
@Import注解
导入类的类型
普通类
improtSelector的实现类
重写selectImport方法,返回字符串数组(全限定类名)
@Conditional注解添加生成条件
importBeanDefinitionRegistrar的实现类
是@Bean的方法的补充,不是来导入配置类的,注入的是bean
Spring Bean的生命周期
其定义为:从对象的创建到销毁的过程。而Spring中的一个Bean从开始到结束经历很多过程,但总体可以分为六个阶段Bean定义、实例化、属性赋值、初始化、生存期、销毁。
Spring Bean 的生命周期可以被划分为以下阶段: 实例化:当容器实例化一个 Bean 时,会使用所配置的 Bean 定义创建一个新的 Bean 实例。 属性赋值:容器通过 Bean 的 setter 方法或者直接字段注入的方式,将配置文件中的属性值注入到 Bean 实例中。 Aware 接口回调:如果 Bean 实现了 Aware 接口,容器将在此阶段调用相关的回调方法,让 Bean 了解所在的容器的信息。 BeanPostProcessor 前置处理器:容器在此阶段调用所有注册的 BeanPostProcessor 的 postProcessBeforeInitialization() 方法对 Bean 进行处理。 初始化方法调用:如果 Bean 实现了 InitializingBean 接口或者配置了 init-method 属性,容器将在此阶段调用其初始化方法进行初始化。 BeanPostProcessor 后置处理器:容器在此阶段调用所有注册的 BeanPostProcessor 的 postProcessAfterInitialization() 方法对 Bean 进行处理。 使用:此时,Bean 已经可以被应用程序使用。 销毁方法调用:如果 Bean 实现了 DisposableBean 接口或者配置了 destroy-method 属性,容器将在此阶段调用其销毁方法进行清理工作。 需要注意的是,如果 Bean 在容器中被创建了多个实例,那么每个实例的生命周期都是相互独立的。同时,如果 Bean 实现了 InitializingBean 和 DisposableBean 接口,同时又配置了 init-method 和 destroy-method 属性,那么在初始化和销毁时都会被调用。
图片解释
子主题
简单实现
创建BeanDefination阶段
BeanFactory通过xml、注解配置类创建BeanDefination
通过BeanFactoryPostProcessor.postProcessBeanFactory()对BeanFination进行修改
创建Bean阶段
反射调用构造函数,创建初始化的Bean对象
注入@Autowired
注入Aware对象
BeanPostProcessor的postProcessBeforeInitialization()
具体来说,postProcessBeforeInitialization() 方法会在 Bean 的 init 方法之前被调用,可以用来修改 Bean 的属性或者执行一些初始化操作。通常情况下,我们会在该方法中对 Bean 进行一些必要的初始化工作,比如对属性进行赋值、对依赖进行注入等。
@PostConstruct
@PostConstruct是一个注解,它表示在该类的实例化过程中,当所有的依赖注入完成之后,会自动执行被该注解标注的方法。通常用于一些初始化操作,比如初始化一些属性、建立数据库连接、启动定时任务等。 在Spring中,@PostConstruct通常与@Autowired一起使用,@Autowired用于自动注入依赖,@PostConstruct则用于在依赖注入完成后做一些初始化操作
InitializingBean.afterPropertiesSet()
InitializingBean是Spring框架提供的一个接口,它包含了一个名为afterPropertiesSet的方法。当一个bean实现了InitializingBean接口并注册到Spring容器中时,在该bean的所有属性都被注入完成后,Spring容器会自动调用该bean的afterPropertiesSet方法,以便在bean初始化完成之后执行一些特定的操作。
<bean init-method>
BeanPostProcessor.postProcessAfterInitialization()
销毁Bean阶段
@PreDestroy
DisposableBean.destroy()
<bean destroy-method>
aop
ioc
springboot和springMVC的区别
Spring Boot和Spring MVC是两个不同的框架,它们在实现Web应用程序时具有不同的设计理念和工作方式。 Spring MVC是一个Web框架,它建立在Spring Framework之上,提供了一个MVC(模型-视图-控制器)架构,用于开发Web应用程序。在Spring MVC中,开发人员需要手动配置一些组件,例如控制器、视图解析器、异常处理器、拦截器等。Spring MVC具有很高的灵活性,可以按照需求进行定制,但是需要较多的配置。 Spring Boot是一个基于Spring Framework的快速应用程序开发框架,它的设计理念是“约定优于配置”,意味着在开发Spring Boot应用程序时,不需要手动配置大量的组件,而是使用默认设置和自动配置来简化开发过程。Spring Boot还提供了许多便捷的功能,例如应用程序监视、管理和部署等。相比之下,Spring Boot的配置比较少,但是灵活性相对较低。 使用Spring MVC: ●开发人员需要手动配置控制器、视图解析器、异常处理器、拦截器等组件。 ●在控制器中,处理HTTP请求和响应,包括获取请求参数、调用服务层逻辑、渲染视图等。 ●在视图解析器中,根据逻辑视图名称查找相应的视图文件,将数据渲染到HTML或JSON格式的响应中。 ●在异常处理器中,处理应用程序中发生的异常,例如500 Internal Server Error等。 ●在拦截器中,拦截HTTP请求并进行预处理,例如身份验证、日志记录等。 使用Spring Boot: ●开发人员不需要手动配置控制器、视图解析器、异常处理器、拦截器等组件,使用Spring Boot的自动配置功能即可。 ●开发人员可以使用Spring Boot提供的各种便捷功能,例如监视、管理、部署等。 ●在控制器中,处理HTTP请求和响应,包括获取请求参数、调用服务层逻辑、渲染视图等。 ●在视图中,使用模板引擎和数据绑定等功能,将数据渲染到HTML或JSON格式的响应中。 ●在服务层中,处理业务逻辑,例如订单处理、库存管理、商品推荐等。
bean默认是单例还是多例,为什么单例多线程访问不出问题
spring中,默认情况下是单例的,即在容器中只存在一个实例。这种单例模式是通过在容器启动时创建 Bean 的实例并放入容器中,随后每次从容器中获取该 Bean 的实例时,都是返回同一个实例。
单例模式在多线程情况下可能出现问题,spring做了一些措施
创建的时候
在创建单例bean的时候,会使用线程安全的方式创建和初始化bean的实例,包括使用非同步锁和非锁定方式,确保多线程创建和初始化bean的安全性
对bean的属性值做修改
Spring 中的 Bean 默认是不可变的,也就是说 Bean 的状态不会被多个线程同时修改,因此在多线程环境下访问同一个 Bean 实例时不会出现线程安全问题。 需要注意的是,如果单例 Bean 中包含了可变状态或共享资源,那么就需要使用同步机制来保证多线程访问的线程安全性。同时,如果需要在多线程环境下使用 Bean,也可以考虑使用原型 Bean,即每次从容器中获取 Bean 时都创建一个新的实例,从而避免多个线程之间的共享问题。
拦截器和过滤器的区别和作用
执行顺序不同
拦截器是在DispatcherServlet之后、Controller之前执行的,
而过滤器是在DispatcherServlet之前执行的
范围不同
拦截器只能拦截controller请求
过滤器可以拦截所有的请求
目的不同
拦截器
拦截器主要作用于控制器方法的执行,可以对控制器方法的参数、返回值和异常等进行拦截处理,对请求进行判断、验证、修改等操作。拦截器需要实现Spring框架中的HandlerInterceptor接口,可以通过配置文件或注解来实现拦截器的注册和配置。拦截器可以进行更加灵活的处理,例如,可以根据不同的请求URL、请求参数、请求头等信息来动态决定是否拦截请求,并可以进行更加复杂的业务处理。
过滤器
过滤器是Servlet规范中的组件,主要作用于请求和响应的处理,可以在请求到达目标资源之前或者之后对请求和响应进行处理,例如,修改请求和响应的编码、处理请求和响应的头部信息、进行安全验证、进行日志记录等。
springCloud
网关
gateway作用
权限控制
网关作为微服务入口,需要校验用户是是否有请求资格,如果没有则进行拦截。
路由和负载均衡
一切请求都必须先经过gateway,但网关不处理业务,而是根据某种规则,把请求转发到某个微服务,这个过程叫做路由。当然路由的目标服务有多个时,还需要做负载均衡。
限流
当请求流量过高时,在网关中按照下流的微服务能够接受的速度来放行请求,避免服务压力过大。
springcloud有两种网关
还有zuul
Zuul是基于Servlet的实现,属于阻塞式编程。而SpringCloudGateway则是基于Spring5中提供的WebFlux,属于响应式编程的实现,具备更好的性能。
跨域
前提
浏览器发的ajax请求
域名、端口、协议任一不同
具体限制跨域流程
浏览器发送的请求已经到达了服务端,但是返回的消息能不能被浏览器接收,需要看返回的相应有没有携带允许跨域的相应头
如何解决
在gateway服务的application.yml文件中,设置一个请求头,表明允许哪些网站的跨域请求
服务调用
fegin
如何实现负载均衡的
SpringCloud底层其实是利用了一个名为Ribbon的组件,来实现负载均衡功能的。
具体的过程是
LoadBalancerInterceptor,这个类会在对请求进行拦截,然后从nacos根据服务id获取服务列表,随后利用负载均衡策略得到真实的服务地址信息,替换服务名。
默认的负载均衡策略
以区域可用的服务器为基础进行服务器的选择。使用Zone对服务器进行分类,这个Zone可以理解为一个机房、一个机架等。而后再对Zone内的多个服务做轮询。
注册中心
nacos
注册中心的原理
nacos注册中心分别服务于两个方面,服务消费者和服务提供者
首先,在各自服务启动的时候,分别都向注册中心注册
在服务消费者发起调用的时候,先向注册中心依据服务名获取服务提供者真实的ip和端口号,如果有多个服务提供者,根据ribbin负载均衡策略选择一个ip访问
而服务提供者通过心跳机制和nacos主动问询来保证nacos注册的服务都是可用的
为啥可以使用服务名来调用服务
这是因为在nacos中,服务提供者会将自己的服务注册到nacos注册中心并指定服务名称
当服务消费者需要调用某个服务时,它可以通过nacos提供的API获取该服务的服务实例列表。nacos会根据服务名称查询注册中心中与该名称相同的服务提供者实例,然后返回这些实例的IP地址和端口号。服务消费者可以选择其中一个实例来进行服务调用。
好处
在服务提供者的IP地址和端口号发生变化时,服务消费者不需要修改代码,只需重新注册到nacos中即可
如何保证被调用服务的可用性
检测异常
首先nacos有两种实例类型,分别是临时实例和非临时实例,对应了不同的检测方式
对于临时实例来说
采用的是心跳模式,服务端需要定期向nacos发送一个心跳,来声明他的状态,一旦心跳不正常,naocs会主动提出这个实例
对与非临时实例
采用的是主动检测模式,主动向服务者发送消息,然后服务者给nacos一个回复,且一旦检测到异常,服务者不再向ancos回应消息时,nacos不会在服务列表中删除这个实例
主动推送
将nacos服务列表变更的消息推送给消费者,防止消费者对服务者存有缓存,依据缓存再向这个服务者发请求
负载均衡策略包含哪些
配置中心
nacos配置中心
微服务保护
微服务监控
解决雪崩问题
什么是雪崩
- 微服务之间相互调用,因为调用链中的一个服务故障,引起整个链路都无法访问的情况。
预防
限流
限制业务访问的QPS,避免服务因流量的突增而故障。
限流是对服务的保护,避免因瞬间高并发流量而导致服务故障,进而避免雪崩。是一种预防措施。
补救
超时处理
时间不好定,一般不超过三秒,一定要加超时时间
舱壁模式
我们可以限定每个业务能使用的线程数,避免耗尽整个tomcat的资源,因此也叫线程隔离。
用多个不同的线程池,不方便
线程隔离
调用者在调用服务提供者时,给每个调用的请求分配独立线程池,出现故障时,最多消耗这个线程池内资源,避免把调用者的所有资源耗尽。
断路器
断路器模式:由断路器统计业务执行的异常比例,如果超出阈值则会熔断该业务,拦截访问该业务的一切请求。
熔断降级
是在调用方这边加入断路器,统计对服务提供者的调用,如果调用的失败比例过高,则熔断该业务,不允许访问该服务的提供者了。
sentinel
限流(预防)
流控模式
直接模式
统计当前资源的请求,触发阈值时对当前资源直接限流,也是默认的模式
关联模式
统计与当前资源相关的另一个资源(任意,没有指定),触发阈值(按照另一个资源计算)时,对当前资源限流
使用场景
比如用户支付时需要修改订单状态,同时用户要查询订单。查询和修改操作会争抢数据库锁,产生竞争。业务需求是优先支付和更新订单的业务,因此当修改订单业务触发阈值时,需要对查询订单业务限流。
满足条件
两个有竞争关系的资源
一个优先级较高,一个优先级较低
链路模式
统计从指定链路访问到本资源的请求,触发阈值(按照指定的链路计算,是由指定链路访问当前资源)时,对指定链路限流
流控效果
快速失败
达到阈值后,新的请求会被立即拒绝并抛出FlowException异常。是默认的处理方式。
warm up
阈值一般是一个微服务能承担的最大QPS,但是一个服务刚刚启动时,一切资源尚未初始化(冷启动),如果直接将QPS跑到最大值,可能导致服务瞬间宕机。 warm up也叫预热模式,是应对服务冷启动的一种方案。请求阈值初始值是 maxThreshold / coldFactor,持续指定时长后,逐渐提高到maxThreshold值。而coldFactor的默认值是3. 例如,我设置QPS的maxThreshold为10,预热时间为5秒,那么初始阈值就是 10 / 3 ,也就是3,然后在5秒后逐渐增长到10.
预热模式,对超出阈值的请求同样是拒绝并抛出异常。但这种模式阈值会动态变化,从一个较小值逐渐增加到最大阈值。
排队等待
让所有的请求按照先后次序排队执行,两个请求的间隔不能小于指定时长
热点参数限流
而热点参数限流是分别统计参数值相同的请求,判断是否超过QPS阈值。
全局参数限流
不加参数类型,对所有的参收都做限制
热带参数限流
指定某个参数,当携带指定的参数访问时,做限制
限流算法
令牌桶
有一个固定容量的桶。系统以恒定速率向桶中放令牌,如果桶满了,令牌就丢弃。 请求到来的时候,首先是从桶中取一个令牌,如果能取哈牌,则允许访问系统,否则,拒绝。 优点:允许流量的突发,这种限流算法是更符合用户访问业务系统的场景的。
漏斗桶
有一个固定大小的桶,请求到来的时候,先往桶中放,如果桶满了,放不进去了,则拒绝 桶以恒定的速率往外输出请求,请求业务系统. 用于场景:百度来调用我们公司的一个接口,百度的流量是非常大的,如果不加限制,从百度直接调用我们公司的接口,很容易就把 接口打挂掉,因此,百度就可以使用漏斗桶,以我们系统可以接收的QPS来阔用。主要是用来保护被调用方。
滑动窗
隔离和熔断(避免雪崩,对调用方做保护)
线程隔离(舱壁模式)两种手段
线程池隔离
优点:基于线程池模式,有额外开销,但隔离控制更强
信号量隔离(sentine默认采用)
不创建线程池,而是计数器模式,记录业务使用的线程数量,达到信号量上限时,禁止新的请求。
优点轻量级,无额外开销,不支持主动超时,不支持异步调用,适用在高频调出
具体使用:在添加限流规则的时候,可以选择两种阈值类型,一种就是线程数,实现了线程隔离
断路器熔断降级的三种策略
熔断器原理

使用aop来监控请求的调用,假如达到降级的要求,则开启熔断,直接走降级的逻辑,而不会去调用后端的服务。 当熔断的时间过后,又有新的请求到来,则会放行一次请求到后端的服务,如果本次请求成功,则认为后端的服务已经恢复正常,然后就会关闭断路器。如果请求失败,则认为后端的服务还是没有恢复,继续执行熔断,走降级的逻辑
慢调用降级
业务的响应时长(RT)大于指定时长的请求认定为慢调用请求。在指定时间内,如果请求数量超过设定的最小数量,慢调用比例大于设定的阈值,则触发熔断。
异常比例降级
统计指定时间内的调用,如果调用次数超过指定请求数,并且出现异常的比例达到设定的比例阈值(或超过指定异常数),则触发熔断。
异常数降级
更多功能
授权规则
定义访问规则
自定义异常结果,出现异常定义返给前端的信息
部署
docker常见命令
解决问题
依赖冲突
放了依赖
操作系统差异
放了库函数
将函数库和依赖配置,应用打包生成一个隔离的容器去运行
架构
镜像(只读)
Docker将应用程序及其所需的依赖、函数库、环境、配置等文件打包在一起,称为镜像。
容器
镜像中的应用程序运行后形成的进程就是容器,只是Docker会给容器进程做隔离,对外不可见。
DockerHub
DockerHub是一个官方的Docker镜像的托管平台。
cs架构
服务端(server):Docker守护进程,负责处理Docker指令,管理镜像、容器等
客户端(client):通过命令或RestAPI向Docker服务端发送指令。可以在本地或远程向服务端发送指令
命令
镜像
拉取镜像
docker pull nginx:版本号
保存镜像
docker save -o [保存的目标文件名称] [镜像名称]
删除镜像
docker rmi nginx:latest
加载本地镜像
docker load -i nginx.tar
容器
创建并运行
docker run --name containerName -p 80:80 -d nginx
- docker run :创建并运行一个容器 - --name : 给容器起一个名字,比如叫做mn - -p :将宿主机端口与容器端口映射,冒号左侧是宿主机端口,右侧是容器端口 - -d:后台运行容器 - nginx:镜像名称,例如nginx
进入容器
docker exec -it mn bash
- docker exec :进入容器内部,执行一个命令 - -it : 给当前进入的容器创建一个标准输入、输出终端,允许我们与容器交互 - mn :要进入的容器的名称 - bash:进入容器后执行的命令,bash是一个linux终端交互命令
查看文件内容cat
修改内容
sed -i -e 's#Welcome to nginx#传智教育欢迎您#g' -e 's#<head>#<head><meta charset="utf-8">#g' index.html
数据卷
创建
docker volume create html
查看所有的
docker volume ls
查看详细的
docker volume inspect html
挂载数据卷
docker run \ --name mn \ -v html:/root/html \ -p 8080:80 nginx \
-v html:/root/html \
挂载本地目录
-v 目录
自定义镜像
Dockerfile
指令

其他指令
基于Ubuntu构建一个java项目
放项目jar包,Dockerfile文件,jdk8压缩包

Dockerfile文件内容
# 指定基础镜像 FROM ubuntu:16.04 # 配置环境变量,JDK的安装目录 ENV JAVA_DIR=/usr/local # 拷贝jdk和java项目的包 COPY ./jdk8.tar.gz $JAVA_DIR/ COPY ./docker-demo.jar /tmp/app.jar # 安装JDK RUN cd $JAVA_DIR \ && tar -xf ./jdk8.tar.gz \ && mv ./jdk1.8.0_144 ./java8 # 配置环境变量 ENV JAVA_HOME=$JAVA_DIR/java8 ENV PATH=$PATH:$JAVA_HOME/bin # 暴露端口 EXPOSE 8090 # 入口,java项目的启动命令 ENTRYPOINT java -jar /tmp/app.j
构建并运行
docker build -t javaweb:1.0 ./
基于java8构建java项目
Dockerfile文件内容
FROM java:8-alpine COPY ./app.jar /tmp/app.jar EXPOSE 8090 ENTRYPOINT java -jar /tmp/app.jar
只放一个项目jar包即可
Docker-Compose
快速部署分布式应用
具体格式
version: "3.8" services: mysql: image: mysql:5.7.25 environment: MYSQL_ROOT_PASSWORD: 123 volumes: - "/tmp/mysql/data:/var/lib/mysql" - "/tmp/mysql/conf/hmy.cnf:/etc/mysql/conf.d/hmy.cnf" web: build: . ports: - "8090:8090"
build的内容
FROM java:8-alpine COPY ./app.jar /tmp/app.jar ENTRYPOINT java -jar /tmp/app.jar
详细语法
默认文件名docker-compose.yml
容器之间互联不通过ip通过容器名即可
spring: datasource: url: jdbc:mysql://mysql:3306/cloud_order?useSSL=false username: root password: 123 driver-class-name: com.mysql.jdbc.Driver application: name: orderservice cloud: nacos: server-addr: nacos:8848 # nacos服务地址
不对外无需暴露端口
部署
docker-compose up -d
docker镜像仓库
推送
docker tag registry:latest 192.168.254.128:8080/registry
重命名,名称前缀是私有仓库地址
docker push 192.168.254.128:8080/registry
拉取
docker pull 192.168.150.101:8080/nginx:1.0
linux常见命令
查进程 ps -ef | grep 进程名 查端口 netstat -anp | grep 端口号 查内存 free -h 查硬盘 df -h 查找大文件 从根目录开始,查找所有文件大小大于100MB的文件并列出路径 find / -type f -size +100M 列出当前目录下最大的10个文件的路径和大小 du -a . | sort -n -r | head -n 10 杀死关于该软件的所有进程 ps -ef | grep 软件名| grep -v grep | awk '{print $2}' | xargs kill -9
docker的volume作用
将容器和数据分离,解耦合
方便操作容器内数据
volume挂载与直接挂载宿主机的目录有啥区别
数据卷挂载耦合度低,由docker来管理目录,但是目录较深,不好找
目录挂载耦合度高,需要我们自己管理目录,不过目录容易寻找查看
docker为啥可以跨操作系统运行
- Docker将用户程序与所需要调用的系统(比如Ubuntu)函数库一起打包 - Docker运行到不同操作系统时,直接基于打包的函数库,借助于操作系统的Linux内核来运行
docker作用
Docker解决依赖兼容问题
Docker为了解决依赖的兼容问题的,采用了两个手段: - 将应用的Libs(函数库)、Deps(依赖)、配置与应用一起打包 - 将每个应用放到一个隔离容器去运行,避免互相干扰
解决操作系统环境差异
- Docker将用户程序与所需要调用的系统(比如Ubuntu)函数库一起打包 - Docker运行到不同操作系统时,直接基于打包的函数库,借助于操作系统的Linux内核来运行
和虚拟机的区别
- docker是一个系统进程;虚拟机是在操作系统中的操作系统 - docker体积小、启动速度快、性能好;虚拟机体积大、启动速度慢、性能一般
中间件
分布式缓存
redis
什么叫缓存的穿透与击穿?雪崩?
穿透
缓存的穿透指的是在查询一个数据时,缓存和数据库中都没有该数据,这样的查询会穿透到后端的数据库中,导致数据库负载过高,影响系统性能。
击穿
缓存的击穿指的是在高并发情况下,某个热点数据失效,导致大量请求直接穿透到后端的数据库中,造成数据库压力过大,甚至宕机。
雪崩
缓存雪崩是指在某一个时间段,缓存中的大量数据同时失效,导致大量请求直接穿透到后端的数据库中,造成数据库压力过大,甚至宕机。这种情况下,整个系统的性能都会受到极大的影响。
解决
为了避免缓存的穿透、击穿和雪崩,可以采取多种策略,例如使用布隆过滤器、设置热点数据永不过期、使用分布式锁等。
redis过期删除策略
如何理解redis是单线程的
Redis是单线程的,这意味着Redis服务器在任何时候只处理一个客户端请求。 这种设计是为了避免并发访问引起的竞争条件和死锁问题,同时使得Redis的实现更加简单和高效。Redis的瓶颈通常不在CPU,而在于网络带宽和硬盘I/O。因此,多线程反而会增加竞争和上下文切换的开销,降低性能。 Redis还提供了多种异步I/O模型和事件驱动机制,可以在单线程的基础上充分利用多核CPU和网络带宽,提高并发处理能力。 同时,Redis还提供了Lua脚本和事务等高级功能,可以在单线程的限制下实现复杂的业务逻辑。
总结
现象
redis是单线程的,意味着在任意时刻,线程只处理一个客户端的请求
目的
防止多线程访问导致竞争问题和死锁情况,不用去考虑各种锁的问题,不存在加锁释放锁操作
避免了不必要的上下文切换消耗 CPU
单线程能够保证高性能
多路复用技术,通过多路复用器(epoll函数)能够在一个线程中监听多个描述符的读写情况
在内存中操作数据,访问数据速度非常快
缺陷
缺陷不在于单线程,在于磁盘io和网络带宽
耗时的命令会导致并发的下降,不只是读并发,写并发也会下降
无法发挥多核 CPU 性能,不过可以通过在单机开多个 Redis 实例来完善。
其他具体介绍
单线程处理的缺点?
耗时的命令会导致并发的下降,不只是读并发,写并发也会下降
无法发挥多核 CPU 性能,不过可以通过在单机开多个 Redis 实例来完善。
为什么不采用多进程或多线程处理?
多线程处理可能涉及到锁。
多线程处理会涉及到线程切换而消耗 CPU。
保证高吞吐量
io多路复用技术
Redis 采用网络 I/O 多路复用技术,来保证在多连接的时候系统的高吞吐量。关于 I/O 多路复用(又被称为“事件驱动”),首先要理解的是,操作系统为你提供了一个功能,当你的某个 socket 可读或者可写的时候,它可以给你一个通知。这样当配合非阻塞的 socket 使用时,只有当系统通知我哪个描述符可读了,我才去执行 read 操作,可以保证每次 read 都能读到有效数据而不做纯返回 -1 和 EAGAIN 的无用功,写操作类似。 操作系统的这个功能是通过 select/poll/epoll/kqueue 之类的系统调用函数来实现,这些函数都可以同时监视多个描述符的读写就绪状况,这样,多个描述符的 I/O 操作都能在一个线程内并发交替地顺序完成,这就叫 I/O 多路复用。多路—指的是多个 socket 连接,复用—指的是复用同一个 Redis 处理线程。多路复用主要有三种技术:select,poll,epoll。epoll 是最新的也是目前最好的多路复用技术。
且 Redis 在内存中操作数据的速度非常快,也就是说内存内的操作不会成为影响 Redis 性能的瓶颈,
避免了串行控制的开销
Redis 是单线程的,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗。
数据结构简单,对数据操作也简单,Redis 中的数据结构是专门进行设计的。
Redis 直接自己构建了 VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求。
基本模型
Redis 客户端对服务端的每次调用都经历了发送命令,执行命令,返回结果三个过程。其中执行命令阶段,由于 Redis 是单线程来处理命令的,所有到达服务端的命令都不会立刻执行,所有的命令都会进入一个队列中,然后逐个执行,并且多个客户端发送的命令的执行顺序是不确定的,但是可以确定的是不会有两条命令被同时执行,不会产生并发问题,这就是 Redis 的单线程基本模型。
Redis 客户端对服务端的每次调用都经历了发送命令,执行命令,返回结果三个过程。其中执行命令阶段,由于 Redis 是单线程来处理命令的,所有到达服务端的命令都不会立刻执行,所有的命令都会进入一个队列中,然后逐个执行,并且多个客户端发送的命令的执行顺序是不确定的,但是可以确定的是不会有两条命令被同时执行,不会产生并发问题,这就是 Redis 的单线程基本模型。
Redis如何实现分布式锁?
Redis 可以通过实现分布式锁来实现对资源的并发访问控制,下面是一个基本的分布式锁实现思路: 在 Redis 中使用一个特定的键作为锁,这个键的值可以是任意字符串,但需要保证它在所有客户端之间是唯一的。 当一个客户端需要获取锁时,它通过 Redis 的 SETNX 命令来设置这个键。如果 SETNX 命令返回 1,表示这个客户端成功获取到了锁,否则表示锁已经被其他客户端获取,需要等待。 为了避免锁被某个客户端持有的时间过长,我们需要为锁设置一个过期时间,如果锁的过期时间到了,那么这个锁就会自动释放。 当一个客户端需要释放锁时,它可以使用 Redis 的 DEL 命令来删除这个键,释放锁。 在实际使用中,可以将代码封装成一个工具类,供需要使用分布式锁的地方进行调用。需要注意的是,在使用 Redis 实现分布式锁时,需要注意避免死锁、多客户端同时获取锁等问题,这些问题需要结合具体的业务场景进行分析和解决。
原理
通过redis的SETNX命令
加锁
执行SETNX命令,返回1成功,返回0失败
设置过期时间
防止占用时间过长
释放锁
执行DEL命令删除这条数据
Redis中数据类型
1. 字符串(string):字符串是最基本的数据类型,在 Redis 中,字符串可以存储任何类型的数据,包括二进制数据。底层数据结构是简单动态字符串(SDS)。 2. 列表(list):列表是一个有序的字符串列表,可以在列表的两端进行插入和删除操作。底层数据结构是双向链表。 3. 集合(set):集合是一个无序的字符串集合,可以进行交集、并集等操作。底层数据结构是哈希表。 4. 哈希表(hash):哈希表是一个键值对的集合,可以用于存储对象。底层数据结构是哈希表。 5. 有序集合(sorted set):有序集合是一个字符串集合,每个字符串都关联一个分数,可以根据分数进行排序和检索。底层数据结构是跳跃表和哈希表。
String
它可以存储任意类型的数据,包括数字、文本、序列化的对象等。Redis中的字符串最大可以存储512MB的数据。
String类型一般用于缓存、限流、计数器、分布式锁、分布式Session。
Hash
通常用来存储对象型数据,如用户信息的对象数据 人(属性,值,属性,值)。
List
List类型是一个有序的字符串列表,支持在列表的头部或尾部添加元素,也支持在列表任意位置插入或删除元素。
List类型一般用于简单队列、列表显示、关注列表、粉丝列表、留言评价…分页、热点新闻等。
可以作为轻量级消息队列,从列表的尾部插入数据,从头部取出数据
set
Set类型一般用于赞、踩、标签、好友关系等;
利用唯一性统计独立IP等;
利用对交集、并集、差集的计算对数据进行过滤处理,如共同好友、推荐信息的数据过滤等。
zset
Zset类型一般用于排行榜、商品进行排序显示等。
地理空间(GEO)
提供了经纬度设置,查询,范围查询,距离查询,经纬度Hash等常见操作。
Bitmaps
位图不是实际的数据类型,而是在 String 类型上定义的一组面向位的操作,将其视为位向量。由于字符串是二进制安全 blob,其最大长度为 512 MB,因此它们适合设置最多 2^32 个不同位。
主要适用用户登陆
HyperLogLog
HyperLogLog是一种基数估计算法,它可以在只使用很少的内存空间的情况下,近似地估计一个集合中不重复元素的数量
steam
是一个消息队列,但是list只能由一个消费者消费,不能广播。
集群相关
主从集群同步的原理
主从同步主要分为两类,分别是
全量同步
全量同步,将master节点的所有数据都拷贝给slave节点,
一般用在主从第一次同步的时候
增量同步
就是只更新slave与master存在差异的部分数据
实际在使用中主要用的是增量同步,这是因为
全量同步需要先做RDB,然后将RDB文件通过网络传输个slave,成本太高了。因此除了第一次做全量同步,其它大多数时候slave与master都是做增量同步。
它的具体流程分两部分,先判断然后做同步
前提先判断之前是不是连接过
slave发送数据
从节点先向主节点发送一个Replication Id和offset,分别表示数据集的标记和数据在baklog中的偏移量
因为slave原本也是一个master,有自己的replid和offset,当第一次变成slave,与master建立连接时,发送的replid和offset是自己的replid和offset。
master接收数据
master接收到发过来的数据,先判断replicationid是否和自己的一致
master判断发现slave发送来的replid与自己的不一致,说明这是一个全新的slave,master会将自己的replid和offset都发送给这个slave,slave保存这些信息。开启全量同步
一致从baklog中找有没有这个offset,有开启增量同步,没有开启全量同步
然后分别根据同步类型进行同步
如果是全量同步
master执行一个bgsave命令,开启一个子线程将完整内存数据生成RDB,发送RDB到slave
slave清空本地数据,加载master的RDB
master将RDB期间的命令记录在repl_baklog,并持续将log中的命令发送给slave
slave执行接收到的命令,保持与master之间的同步
如果是增量同步
slave发给master的offset值实际记录了在baklog中的偏移量,这个文件是一个固定大小的数组,只不过数组是环形,也就是说角标到达数组末尾后,会再次从0开始读写,这样数组头部的数据就会被覆盖。
master接收到offset从baklog中找这个offset,发送offset后的命令
哨兵集群故障恢复原理
定时检测,发现下线的节点
检测
Sentinel基于心跳机制监测服务状态,每隔1秒向集群的每个实例发送ping命令: 如果某sentinel节点发现某实例未在规定时间响应,则认为该实例主观下线,此时并不是真正的下线,若超过指定数量(quorum)的sentinel都认为该实例主观下线,则该实例客观下线。quorum值最好超过Sentinel实例数量的一半。此时认为这个节点是真正的下线
选择新的master
选择依据
- 首先会判断slave节点与master节点断开时间长短,如果超过指定值(down-after-milliseconds * 10)则会排除该slave节点 - 然后判断slave节点的slave-priority值,越小优先级越高,如果是0则永不参与选举 - 如果slave-prority一样,则判断slave节点的offset值,越大说明数据越新,优先级越高 - 最后是判断slave节点的运行id大小,越小优先级越高。
开始实现切换
具体过程
- 选出一个sentinel做数据切换 - sentinel给备选的slave1节点发送slaveof no one命令,让该节点成为master - sentinel给所有其它slave发送slaveof 192.168.150.101 7002 命令,让这些slave成为新master的从节点,开始从新的master上同步数据。 - 最后,sentinel将故障节点标记为slave,当故障节点恢复后会自动成为新的master的slave节点,在配置文件做一个修改
向cluster中存储一个key的原理?
你们的生产环境的redis是如何部署的
主从集群
结构
一个主节点,对应多个从节点,提高了并发能力,实现了读写分离,能够实现大量读的场景(从节点读),没有故障检测和恢复的能力
同步
详细看主从同步原理
优化
配置文件配置无磁盘复制
repl-diskless-sync yes
单节点内存不能占用大,减少rdb导致的过多磁盘io
适当提高repl_baklog的大小,发现slave宕机时尽快实现故障恢复,尽可能避免全量同步
限制一个master上的slave节点数量,如果实在是太多slave,则可以采用主-从-从链式结构,减少master压力
哨兵集群
架构就是
哨兵集群的结构是一个master对应了多个slave,并且由多个哨兵对集群的每个实例进行监控
选择原因
写少
选择哨兵集群主要是考虑到这个项目没有大量的写的操作,所以仅仅一个master就能够实现写需求,
读多
但是有不少读的场景,所以设置了多个从节点,实现一个读写分离,让主节点做读写操作,满足写的需求,让多个从节点实现大量的读操作,,
监控
并且多个哨兵能够实现监控集群和自动故障恢复
实际这个项目没有海量数据存储和高并发写的需求,如果有这方面的需要就得上分片集群了
分片集群
适用场景
高并发写,海量数据存储
读多
选择分片集群的原因是不仅有大量的读的操作还有大量的写的操作通过布置多个从节点,实现读写分离,让读从从节点读,提高并发能力
写多(海量数据存储)
同时还布置了多个主节点,来满足写多的场景
监控
master之间通过ping监测彼此健康状态
特征
- 集群中有多个master,每个master保存不同数据 - 每个master都可以有多个slave节点 - master之间通过ping监测彼此健康状态 - 客户端请求可以访问集群任意节点,最终都会被转发到正确节点
责任划分
每个分片都由一个主节点和若干个从节点组成,
每个主节点和对应的从节点负责这一个分片的读和写
数据分派
散列插槽
向cluster中存储一个key的原理?
存储数据
- 客户端请求可以访问集群任意节点,最终都会被转发到正确节点
持久化
redis如何保证重启数据不丢失?
对数据做了持久化,有三种持久化方式,rdb,aof,混合持久化
rdb
概念
什么是rdb
rdb就是数据快照,简单来说来说就是把内存中的所有数据都记录到磁盘中。当Redis实例故障重启后,从磁盘读取快照文件,恢复数据
缺点
由于RDB执行间隔时间长,两次RDB之间写入数据有丢失的风险
并且fork子进程、压缩、写出RDB文件都比较耗时
适用场景
适用在可以容忍数分钟的数据丢失,追求更快的启动速度
具体流程
Redis内部有触发RDB的机制,可以在redis.conf文件中设置时间间隔定期执行命令将数据持久化到rdb文件中
原理
Redis使用Copy-On-Write(COW)机制来保证数据的一致性和原子性,具体过程如下: 当Redis需要进行RDB持久化操作时,它会先fork一个子进程来处理持久化操作。 子进程会在内存中创建一个和父进程相同的副本,这时候父进程和子进程共享相同的物理内存。 当父进程需要修改某个key对应的value时,它会先检查该key对应的value是否被其它进程所引用,如果没有,则直接在自己的内存中修改该value的内容,否则就会触发Copy-On-Write机制。 父进程会将需要修改的value的页表项设置为只读,然后fork出一个新的页表项,新页表项指向一个新的物理内存页,将需要修改的value复制到新的物理内存页中,并将新页表项设置为可写,最后再将该key对应的指针指向新的物理内存页。 这时候父进程和子进程分别持有各自不同的value,它们互不干扰,实现了数据的一致性和原子性。 需要注意的是,Copy-On-Write机制只对需要修改的value进行复制,对于没有修改的value,父进程和子进程仍然共享相同的物理内存,避免了大量的复制操作,提高了性能。同时,在处理大量数据时,Copy-On-Write机制也能够有效地减少内存的消耗
当Redis需要进行RDB持久化操作时,它会先fork一个子进程来处理持久化操作。
子进程会在内存中创建一个和父进程相同的副本,这时候父进程和子进程共享相同的物理内存,且内存中的页表处于只读状态
分别开始工作
子进程将这个只读的文件写新的rdb文件到磁盘中,替换掉旧的rdb文件
当父进程要执行写操作时,会创建一个数据副本,将写操作写进这个副本中,主进程重新指向修改后的副本
aof
概念
什么是aof
Redis处理的每一个写命令都会记录在AOF文件,可以看做是命令日志文件。
特点
相较rdb,是记录了 每一次执行的命令,记录相对完整,文件体积大,宕机回复速度慢,系统资源占用低,aof重写会占用大量资源,
使用场景
适用于对数据安全性较高的场景
具体流程
aof在redis中默认是关闭的需要手动打开,
定期执行
刷盘策略
每执行一次写命令,立即记录到aof文件
性能影响大,可靠性高
写命令执行完先放入AOF缓冲区,然后表示每隔1秒将缓冲区数据写到AOF文件,是默认方案
最多丢失1秒数据,性能适中
写命令执行完先放入AOF缓冲区,由操作系统决定何时将缓冲区内容写回磁盘
性能好,可靠性差,可能丢失大量数据
aof文件重写
因为是记录命令,AOF文件会比RDB文件大的多。而且AOF会记录对同一个key的多次写操作,但只有最后一次写操作才有意义。通过执行bgrewriteaof命令,可以让AOF文件执行重写功能,用最少的命令达到相同效果。
重写频率在配置文件中执行
混合持久化
特点
aof-use-rdb-preamble通过配置文件中配置,默认是打开的,当开启了aof和rdb自动开启
RDB和AOF各有自己的优缺点,如果对数据安全性要求较高,在实际开发中往往会结合两者来使用。
原理
Redis在重写 AOF 文件时,会将 RDB 文件的前导部分以文本形式写入 AOF 文件开头,然后写入所有后续的数据库写操作。这样,在恢复数据时,Redis在启动时会首先读取 AOF 文件中的 RDB 文件前导部分并将其加载到内存中,然后再加载剩余写操作。
对比
redis rdb bgsave的原理
是什么
通过bgsave命令redis保存当前内存的全部数据到硬盘中
原理
Redis使用Copy-On-Write(COW)机制来保证数据的一致性和原子性,具体过程如下: 当Redis需要进行RDB持久化操作时,它会先fork一个子进程来处理持久化操作。 子进程会在内存中创建一个和父进程相同的副本,这时候父进程和子进程共享相同的物理内存。 当父进程需要修改某个key对应的value时,它会先检查该key对应的value是否被其它进程所引用,如果没有,则直接在自己的内存中修改该value的内容,否则就会触发Copy-On-Write机制。 父进程会将需要修改的value的页表项设置为只读,然后fork出一个新的页表项,新页表项指向一个新的物理内存页,将需要修改的value复制到新的物理内存页中,并将新页表项设置为可写,最后再将该key对应的指针指向新的物理内存页。 这时候父进程和子进程分别持有各自不同的value,它们互不干扰,实现了数据的一致性和原子性。 需要注意的是,Copy-On-Write机制只对需要修改的value进行复制,对于没有修改的value,父进程和子进程仍然共享相同的物理内存,避免了大量的复制操作,提高了性能。同时,在处理大量数据时,Copy-On-Write机制也能够有效地减少内存的消耗
当Redis需要进行RDB持久化操作时,它会先fork一个子进程来处理持久化操作。
子进程会在内存中创建一个和父进程相同的副本,这时候父进程和子进程共享相同的物理内存,且内存中的页表处于只读状态
分别开始工作
子进程将这个只读的文件写新的rdb文件到磁盘中,替换掉旧的rdb文件
当父进程要执行写操作时,会创建一个数据副本,将写操作写进这个副本中,主进程重新指向修改后的副本
redis aof的刷盘策略
刷盘策略
每执行一次写命令,立即记录到aof文件
性能影响大,可靠性高
写命令执行完先放入AOF缓冲区,然后表示每隔1秒将缓冲区数据写到AOF文件,是默认方案
最多丢失1秒数据,性能适中
写命令执行完先放入AOF缓冲区,由操作系统决定何时将缓冲区内容写回磁盘
性能好,可靠性差,可能丢失大量数据
什么叫混合持久化
Redis支持两种持久化的方式:RDB和AOF,分别指的是
RDB是将Redis在某个时间点的数据集快照持久化到磁盘上,
而AOF则是将Redis执行的每个写命令都记录到一个追加的文件中
为什么用混合持久化
由于这两种持久化各有利弊,RDB 可能会导致一定时间内的数据丢失,而 AOF 由于文件较大则会影响 Redis 的启动速度,为了能同时使用 RDB 和 AOF 各种的优点,Redis 4.0 之后新增了混合持久化的方式。
混合持久化是结合了这两种持久化方式
Redis在重写 AOF 文件时,会将 RDB 文件的前导部分以文本形式写入 AOF 文件开头,然后写入所有后续的数据库写操作。
重启后
这样,在恢复数据时,Redis在启动时会首先读取 AOF 文件中的 RDB 文件前导部分并将其加载到内存中,然后再加载剩余写操作。
异步消息
为什么用mq不用feign直接调用
feign属于同步通讯
同步通讯的坏处
答:高耦合,代码相互关联 性能浪费,一个通信被堵塞了,以后的通信都要等待 性能下降,节点多 级联失败一个失败整个回滚
mq属于异步通讯
异步通讯的好处
答:异步通讯解耦合, 不影响性能,资源不浪费 回滚不受影响 流量削峰,防止大量请求击垮全部的服务
rabbitMQ
异步处理的时候,为什么用mq不用线程池
主要原因 解耦 保证了消息的安全性 降低主服务资源占用 削峰 1.解耦 使用消息队列(MQ)来处理异步消息的主要原因是解耦和削峰。这意味着,发送方和接收方之间可以解耦,因为它们不需要直接交互。而消息队列作为一个中间件来完成这个任务。这种解耦使得系统更加灵活,因为它允许你更容易地添 加或删除处理程序,而不会对发送方或接收方产生太大的影响。 而线程池处理异步消息时,需要在代码中编写处理逻辑并管理线程。这可能会导致代码的错误或难以维护。同时,在高并发的情况下,线程池的数量可能会急剧增加,这可能会导致资源的浪费或甚至系统崩溃。 2消息的可靠性和稳定性 MQ是更安全和可靠的选择通过在生产者发送确认,mq做持久化,消费者消费确认来保证了消息的可靠传递,并且可以具有高可用性和故障恢复能力。而线程池适用于一些简单的异步任务,但不能解决高负载和可扩展性方面的问题。 3降低主服务资源的占用 2.用线程的话,会占用主服务器资源,消息队列的话,可以放到其他机器上运行,让主服务器尽量多的服务其他请求。 4削峰 在访问量剧增的情况下,但是应用仍然需要发挥作用,但是这样的突发流量并不常见。而使用消息中间件采用队列的形式可以减少突发访问压力,不会因为突发的超时负荷要求而崩溃
RabbitMQ如何保证消息的可靠性
生产者确认机制
RabbitMQ提供了publisher confirm机制来避免消息发送到MQ过程中丢失。这种机制必须给每个消息指定一个唯一ID,来区分回执是那个生产者发送的消息。消息发送到MQ以后,会返回结果给发送者,表示消息是否处理成功。
有两个地方会返回结果
消息是否成功投递到交换机
消息投递到交换机了,但是有没有路由到队列
消息持久化机制
在mq中声明的的队列和交换机默认是非持久化的
但是我们都是用的由SpringAMQP声明的交换机和队列和消息,它是默认都是持久化的。
消费者确认机制
RabbitMQ是阅后即焚机制,RabbitMQ确认消息被消费者消费后会立刻删除。 而RabbitMQ是通过消费者回执来确认消费者是否成功处理消息的:消费者获取消息后,应该向RabbitMQ发送回执,表明自己是否处理完消息。
返回消息类型
发送回执的时候有三种消息类型,分别是ack,nack,和reject,ack表示消费成功,返回ackmq会直接将消息删除,如果返回的是nack和reject,且他们的数据requeue值为true,会让mq将消息重新入队,再次发送消息到消费者
ack
消费成功
nack
requeue=true
设置让rabbit再发一次
requeue=false
设置让rabbit丢弃
reject
requeue=true
设置让rabbit再发一次
requeue=false
设置让rabbit丢弃
消费者发送给rabbit也有三种发送方式,分别是
none
MQ假定消费者获取消息后会成功处理,因此消息投递后立即被删除
auto
自动ack,由spring监测代码是否出现异常,没有异常则返回ack;抛出异常则返回nack,且requeue会赋值true,如果消息一直没有没成功消费,spring会一直发送nack,mq会一直向消费者发送消息
我们这时候可以利用Spring的retry机制,在消费者出现异常时利用本地重试,设置一个最大尝试次数,
重试次数耗尽,如果消息依然失败,它包含三种不同的实现: 重试耗尽后,直接返回reject,requeue值为false丢弃消息。默认就是这种方式 重试耗尽后,返回nack,消息重新入队 重试耗尽后,将失败消息投递到指定的交换机
比较推荐的一种处理方案是失败后将消息投递到一个指定的,专门存放异常消息的队列,后续由人工集中处理。
manual
手动ack,需要在业务代码结束后,调用api发送ack。
实现高可用
有两种集群可以实现rabbit的高可用
普通集群
是什么
是一种分布式集群,将队列分散到集群的各个节点,从而提高整个集群的并发能力。
特点
缺点:每个queue没有备份
共享交换机、队列元信息。不包含队列中的消息。原信息就是一个指针,指向队列
当访问集群某节点时,如果队列不在该节点,会从数据所在节点传递到当前节点并返回
队列所在节点宕机,队列中的消息就会丢失
镜像集群
是什么
是一种主从集群,普通集群的基础上,添加了主从备份功能,提高集群的数据可用性。
特点
主从同步并不是强一致
镜像集群虽然支持主从,但主从同步并不是强一致的,某些情况下可能有数据丢失的风险。因此在RabbitMQ的3.8版本以后,推出了新的功能:仲裁队列来代替镜像集群,底层采用Raft强一致协议确保主从的数据一致性。
镜像集群本质上是一个主从模式
主节点会复制队列到其他节点,这种节点叫做该队列的镜像节点,
主节点会在镜像节点之间进行同步备份
一个队列的主节点可能是另一个队列的镜像节点
主宕机后,镜像节点会替代成新的主
优化:仲裁队列
也是基于镜像集群,默认的镜像数是5
使用简单没有复杂配置
采用raft强一致性协议确保主从数据的一致性
简述RabbitMQ的工作流程
工作模式有哪些
答:基本消息模式,工作消息队列,发布订阅,路由模式,主题模式 1基本消息模式:一个生产的将消息发给队列,一个消费者监听队列并获取消息 2工作消息模式:一个生产者将消息发给队列,多个消费者监听队列并获取消息 服务器会以轮询的方式将消息给消费者 3发布订阅模式:生产者将消息转发到路由,路由将这个消息转发给绑定这个路由的全部队列 消费者监听这个队列,服务器将消息转发给消费者 4.路由模式:生产者将消息转发到路由,路由根据发过来的key,找到这个key对应的队列,将消息转发给队列 5.主题模式:消息发给路由后,根据发过来的带有通配符的key,找到符合这个条件的队列并将消息转发过去
常见的mq产品
答:activeMQ基本不用 RabbitMQ部署简单,支持高可用,消息延迟低 rocketMQ Kafka
延迟消息如何实现
- 定时投递:在消息发送时设置消息的投递时间,然后将消息发送到消息队列,到了指定的时间再投递到对应的消费者,最典型的代表是 RabbitMQ 的 Delayed Message Exchange 插件。 - 死信队列(DLQ):在正常情况下,消息发送到一个队列,如果不符合要求(例如30分钟不支付订单),则被推到由 MQ 管理的另一个队列中,这个队列就是死信队列,取出这个队列中的消息进行处理。Redisson 已经集成了 DLQ。 不推荐使用死信队列,存在一个队头堵塞问题,队头的消息没有被消费,即使其他消息到了过期时间,也不会被消费
子主题
kafka
如何实现kafka高性能
影响性能的三方面:网络,磁盘,复杂度
网络
网络模型
磁盘
顺序写
完成一次磁盘io,需要经过寻道,旋转,数据传输三个方面
寻道时间:Tseek 是指将读写磁头移动至正确的磁道上所需要的时间。寻道时间越短,I/O 操作越快,目前磁盘的平均寻道时间一般在 3-15ms。 旋转延迟:Trotation 是指盘片旋转将请求数据所在的扇区移动到读写磁盘下方所需要的时间。旋转延迟取决于磁盘转速,通常用磁盘旋转一周所需时间的 1/2 表示。比如:7200rpm 的磁盘平均旋转延迟大约为 60*1000/7200/2 = 4.17ms,而转速为 15000rpm 的磁盘其平均旋转延迟为 2ms。 数据传输时间:Ttransfer 是指完成传输所请求的数据所需要的时间,它取决于数据传输率,其值等于数据大小除以数据传输率。目前 IDE/ATA 能达到 133MB/s,SATA II 可达到 300MB/s 的接口数据传输率,数据传输时间通常远小于前两部分消耗时间。简单计算时可忽略。
因此,如果在写磁盘的时候省去寻道、旋转可以极大地提高磁盘读写的性能。
Kafka 采用顺序写文件的方式来提高磁盘写入性能。顺序写文件,基本减少了磁盘寻道和旋转的次数。磁头再也不用在磁 道上乱舞了,而是一路向前飞速前行。 Kafka 中每个分区是一个有序的,不可变的消息序列,新的消息不断追加到 Partition 的末尾,在 Kafka 中 Partition 是一个逻辑概念,Kafka 将 Partition 划分为多个 Segment,每个 Segment 对应一个物理文件,Kafka 对 segment 文 件追加写,这就是顺序写文件。
复杂度
零拷贝
Kafka Consumer 从 Broker 消费数据,Broker 读取 Log,就使用了 sendfile。
数据要copy4次
第一次:读取磁盘文件到操作系统内核缓冲区; 第二次:将内核缓冲区的数据,copy 到应用程序的 buffer; 第三步:将应用程序 buffer 中的数据,copy 到 socket 网络发送缓冲区; 第四次:将 socket buffer 的数据,copy 到网卡,由网卡进行网络传输。
什么是零拷贝
零拷贝就是尽量去减少上面数据的拷贝次数,从而减少拷贝的 CPU 开销,减少用户态内核态的上下文切换次数,从而优化数据传输的性能。
常见思路
直接 I/O:数据直接跨过内核,在用户地址空间与 I/O 设备之间传递,内核只是进行必要的虚拟存储配置等辅助工作; 避免内核和用户空间之间的数据拷贝:当应用程序不需要对数据进行访问时,则可以避免将数据从内核空间拷贝到用户空间; 写时复制:数据不需要提前拷贝,而是当需要修改的时候再进行部分拷贝。
保证消息可靠性
producer生产者
生产者可以设置一个ack确认机制,有三种模式: 不需要broker确认, 需要brokder的leader确认, 需要leader和ISR确认, 开启这个确认功能,后可以通过两种方式来获取结果: 1.同步方式发送消息时,在发送完调用完send方法后,对方法结果调用一个get方法来获取结果 2.异步方式发送消息,在调用send方法,向内部传一个callback对象,重写oncompletion方法,定义好对结果的处理,发送失败后就会执行里面的逻辑 支持重试次数,可以设置一个最大重试次数,需要在消费者端做一个幂等处理
borker工作
数据持久化
当消息传到topic中时,实际是将消息存进了以topic-partition命名的真实存在的partition目录下 ,这个目录中主要有两种文件分别是index文件和log文件: .index文件 就是记录了offset值和对应的在log文件中的起始位置(字节数,是位置),采用了稀疏索引,并没有将所有的offset都记录下来,当需要查找的时候,采用了二分查找,根据offset值判断在那两个offset值之间,就能快速找到一个范围 .log文件 日志文件,记录了消息和offset值,可以根据offset值获取数据,kafka会不停的在log文件后面追加消息,直到超过最大值1G,然后另起一个文件,文件名记录了这个文件开始的offset值,方便快速查找
消息回溯
消息不会一直不停的在log文件中追加,而是每隔7天清理一次,与rabbit不同,kafka不是采用的阅后即焚机制,而是一直保存,在7天之内,只要知道offset值,即便是消费者消费了,也可以获取消息
高可用
leader和follower分离: 以partition实现高可用,partition分成两种,具有读写权限的leader,仅作备份的follower,原理是将partition打散分到不同的集群,且数据写入后,leader会将数据同步给follower中,这样一个节点leader故障还有另一个节点的follower可用 选主: follower有两种ISR(同步快,性能好,同步),普通(性能相对差,异步同步), (消息确认时,ack机制有一个all模式,是leader+ISR都给生产者确认) 宕机会从ISR中选一个成为leader
消费者
enable.auto.commit:是否自动提交消费者的offset, auto.commit.interval.ms:自动提交的时间间隔 做可靠性要做手动提交,先禁用以上功能,消息消费完成后再手动向borker发送offset,表示成功消费这条消息 提交方式有三种,同步提交和异步提交和组合提交,分别执行commitSync()和commitAsync()方法, 同步提交 同步模式下提交失败的时候一直尝试提交,直到遇到无法重试的情况下才会结束,同时同步方式下消费者线程在拉取消息会被阻塞,在broker对提交的请求做出响应之前,会一直阻塞直到偏移量提交操作成功或者在提交过程中发生异常,限制了消息的吞吐量。 异步提交 异步提交也有个缺点,那就是如果服务器返回提交失败,异步提交不会进行重试。相比较起来,同步提交会进行重试直到成功或者最后抛出异常给应用。异步提交没有实现重试是因为,如果同时存在多个异步提交,进行重试可能会导致位移覆盖。 举个例子,假如我们发起了一个异步提交commitA,此时的提交位移为2000,随后又发起了一个异步提交commitB且位移为3000;commitA提交失败但commitB提交成功,此时commitA进行重试并成功的话,会将实际上将已经提交的位移从3000回滚到2000,导致消息重复消费。 同步和异步组合提交 先异步提交,然后在finally中加一次同步提交 中间处理消息的时候,即使偶尔出现一次偏移量提交失败,后面消费的时候,偏移量也能够提交成功,所以不会有大影响;但是到了最后消费者要关闭了的时候,偏移量一定要提交成功。 因此, 在消费者关闭前一般会组合使用 commitAsync() 和 commitsync() 。 同步一定会提交成功,异步可能会失败
实现顺序消费
消息是在partition的文件末尾一个一个追加,让消息发到同一个partition中
只设置一个partition
指定发给那个partition(不推荐,不知道有几个partition)或者指定一个key(根据key的哈希值和partition的数量做对应)
根据key计算hash取余得到一个partition,推荐,实现指定了发给一个partition,消费的时候,就会从有消息的这个partition取到消息
分布式事务
解决的理论基础
cap理论
分布式事务有三个指标:一致性,可用性,分区容错性,这三个指标不可能同时做到。
系统间的网络不能保证100%健康,一定会有故障的时候,但是必须保证对外提供服务,也就是保证一个分区容错性
如果此时要保证一致性,就必须等待网络恢复,完成数据同步后,整个集群才对外提供服务,服务处于阻塞状态,不可用。
如果此时要保证可用性,就不能等待网络恢复,那node01、node02与node03之间就会出现数据不一致。
也就是说,在p一定的情况下,a和c之间只能实现一个
base理论
BASE理论是对CAP的一种解决思路,包含三个思想:
Basically Available (基本可用):分布式系统在出现故障时,允许损失部分可用性,即保证核心可用。
Soft State(软状态):在一定时间内,允许出现中间状态,比如临时的不一致状态。
Eventually Consistent(最终一致性):虽然无法保证强一致性,但是在软状态结束后,最终达到数据一致。
解决模式
分布式事务最大的问题是各个子事务的一致性问题,因此可以借鉴CAP定理和BASE理论,有两种解决思路
AP模式:各子事务分别执行和提交,允许出现结果不一致,然后采用弥补措施恢复数据即可,实现最终一致。
CP模式:各个子事务执行后互相等待,同时提交,同时回滚,达成强一致。但事务等待过程中,处于弱可用状态。
seata
阿里巴巴共同开源的分布式事务解决方案
seate一共有三种模式
xa
xa模式符合xa规范,大多数主流数据库都支持这种规范,实现的原理基于两阶段提交
seat对原始的xa公式做了简单的封装和改造
具体流程
第一阶段
事务管理器先向事务协调者注册一个全局事务,然后调用各个分支执行本地事务,本地事务执行前,各个资源管理器也向事务协调者注册一个分支事务,本地事务完成后,不会进行提交事务,向事务协调者发送一个事务状态,然后等待事务协调者通知
第二阶段
事务管理器向事务协调者发送给一个提交或者回滚的通知,事务协调者检测各个事务执行状态,如果都成功,通知所有RM提交事务,如果有失败,通知所有RM回滚事务
优缺点
满足了强一致性,且实现简单,没有代码侵入
缺点就是一阶段需要锁定数据库资源,等待二阶段结束后才释放资源,性能较差
这种模式主要适用与对数据一致性要求较高,同时对读操作不多的场景
at
AT模式同样是基于两阶段提交的事务模型,它没有使用db锁实现数据一致性,而是对数据做了备份,使用了自定义的逻辑锁,相交db锁,数据可以随意读,但是不能随意写从而提高了数据库的性能。,具体流程分为两个阶段
一阶段
本地事务完成后立马进行提交事务,,同时在事务提交前后对数据做了备份,将数据存进数据库中的undolog表中,为了防止被seate管理的其他事务对这个中间状态的数据做修改,要求seate的事务在提交本地事务释放DB锁之前,先拿到全局锁,
全局锁
全局锁本质就是在seate库中创建一张lock_table表,记录了这个处于中间状态的数据,这样如果有其他seate事务要修改数据,在提交事务之前,先到这个表中查有没有这个数据,有数据说明有另外一个事务来操作当前数据。
为了防止出现一个死锁状况,即一个事务拿到了db锁想要拿到全局锁提交事务,另一个事务没有提交全局事务,形成了一个死锁,所以对获取全局锁的时间做了限定, 超过一定时间还没有获取全局锁,就不再提交事务
二阶段
事务协调者如果向每个资源管理器发出一个回滚的通知,先跟 先根据将数据库中的数据与afterimage中的数据做比较,如果一致,说明数据没有被修改过,然后再根据beforeimage将数据做恢复
tcc
TCC模式与AT模式非常相似,每阶段都是独立事务,不同的是TCC通过人工编码来实现数据恢复。需要实现三个方法:
Try:资源的检测和预留;
Confirm:完成资源操作业务;要求 Try 成功 Confirm 一定要能成功。
- Cancel:预留资源释放,可以理解为try的反向操作。
数据库
mysql
优化
先看是不是出现了慢查询
开启看系统慢查询日志
explain看sql执行计划,看为什么出现慢查询,主要原因有以下几点
没有加索引
加上索引
查询没有命中索引
优化查询语句,尽量命中索引
使用like关键词查询,匹配符的第一个字符是%索引不起作用
使用多个字段创建索引,提高索引范围
使用or关键字,只有or前后两个都是索引的时候才会命中索引
优化子查询,子查询会建立一个临时表然后从临时表中查数据,查完后再删除一个临时表,影响速度,尽量使用join连接来代替子查询,不需要建立临时表,速度比子查询快
做了多表联查
增加一些冗余字段,
但是现今存储硬件越来越便宜,有时候查询数据的时候需要join多个表,这样在高峰期间会影响查询的效率,我们需要反范式而为之,增加一些必要的冗余字段,以空间换时间需要这样做会增加开发的工作量和维护量,但是如果能换来可观的性能提升,这样做也是值得的
增加中间表
对于需要经常联合查询的表,可以建立中间表以提高查询效率。通过建立中间表,把需要经常联合查询的数据插入到中间表中,然后将原来的联合查询改为对中间表的查询,以此来提高查询效率。
表中有超大型字段
增加缓存
缓存数据存在redis等中间件,减少查询次数
结构
MySQL支持多种不同类型的索引结构,其中最常用的是B树和哈希索引。 B+树索引:B+树索引是一种高效的索引结构,能够在大型数据集上快速定位数据。它是一棵平衡的树形结构,每个节点可以包含多个键值,并且节点之间的高度相同。在MySQL中,InnoDB存储引擎使用的就是B树索引,包括B+树和R树。 哈希索引:哈希索引是另一种常见的索引结构,它通过哈希函数将索引列的值转换成一个固定长度的值,并将该值映射到一个桶中。当需要查找一个特定值时,可以通过哈希函数将该值转换成对应的桶,并在该桶中查找该值。哈希索引的查询效率非常高,但是它无法支持范围查找。 全文索引:全文索引是一种特殊的索引结构,用于在文本数据中进行关键字搜索。它使用一种称为倒排索引(Inverted Index)的结构来存储文本数据,该结构将文本数据中的每个单词映射到包含该单词的所有文档中,以便快速查找包含特定单词的文档。 需要注意的是,索引并不是越多越好。过多的索引可能会增加写入操作的开销,并占用大量磁盘空间。因此,在设计索引时,需要根据具体的业务需求和数据特点来合理地选择索引结构和索引列
两个线程访问同一个表如何避免脏数据
使用事务
在事务中执行多个操作,当所有操作都成功完成后,将所有更改一起提交,如果有任何错误则回滚所有更改。这确保了多个线程不会同时修改同一行
使用锁
乐观锁
1.在数据表中添加一个版本号或时间戳列,每次更新数据时,将版本号或时间戳加1或更新为当前时间。 2.当一个线程要更新数据时,首先读取数据的版本号或时间戳。 3.然后,该线程执行更新操作,并在更新时检查版本号或时间戳是否与读取时的值相同。 4.如果版本号或时间戳匹配,则表示更新成功;否则表示数据已经被其他线程更新,需要回滚事务或重试更新操作。 5.如果重试操作,则重复步骤2至4,直到更新成功或达到重试次数的上限。 使用乐观锁的好处是,可以避免锁定数据和等待锁释放所带来的性能问题,并且多个线程可以同时读取数据,提高了并发性能。但是,如果并发更新操作比较频繁,可能会导致大量的重试操作,影响性能。因此,使用乐观锁时需要根据实际情况选择合适的重试次数和重试策略
悲观锁
在执行操作之前锁定数据,确保其他线程无法修改数据。但是,这可能会导致性能问题,因为多个线程可能需要等待锁的释放。
使用队列
将所有更新操作排队,并使用单个线程依次执行更新操作。这种方法确保了更新操作的顺序,但可能会导致性能问题。
分表分库分区
分表
分表是将一张大表拆分成多个小表存储,每个小表中包含了大表的一部分数据,例如按照用户 ID、日期、地区等维度进行拆分。
好处
提高查询性能,减少锁竞争,降低单表数据量
坏处
增加了数据管理的复杂度和查询的难度
分库
分库是将一个数据库拆分成多个小数据库,每个小数据库中包含了一部分数据表
好处
提高数据库并发性能和数据容量,减少单个数据库的压力
坏处
增加了数据管理的复杂度和事务处理的难度,需要引入分布式事务来保证数据一致性。
分区
分区是将一张大表拆分成多个小分区存储,每个分区包含了大表的一部分数据
好处
分区可以按照数据的范围、时间、哈希等维度进行划分,可以提高查询性能,减少锁竞争,
坏处
增加了数据管理的复杂度和查询的难度。
分表、分库和分区可以同时使用,以实现更高的性能和可扩展性。但是需要注意的是,分割得太细会增加管理的复杂度,分割得太粗会降低性能和扩展性,需要根据实际情况进行设计和调整。
innoDB事物是如何管理的
InnoDB是MySQL的默认事务型存储引擎,支持ACID事务(原子性、一致性、隔离性、持久性)特性,可以确保数据的一致性和完整性。 在InnoDB中,事务是由MySQL服务器自动管理的,开发人员只需要使用MySQL提供的事务语句(如BEGIN、COMMIT、ROLLBACK等)来控制事务的开始、提交和回滚。当一个事务开始时,InnoDB会将所有涉及到的数据块都读入到内存中,并对这些数据块进行锁定,以确保事务对这些数据块的读写操作不会被其他事务干扰。当事务提交或回滚时,InnoDB会根据事务日志来执行相应的操作,包括持久化数据到磁盘或将数据从内存中删除。 在InnoDB中,每个事务都有一个唯一的事务ID(Transaction ID),用于区分不同的事务。事务ID由MySQL服务器分配,每个新的事务ID都比前一个事务ID大。在事务执行过程中,InnoDB会在每个数据块的页头中记录最新的事务ID,以及最新的操作类型(读或写)。当事务提交或回滚时,InnoDB会将事务ID和操作类型写入事务日志中,以确保事务的持久性。 InnoDB还支持MVCC(多版本并发控制)机制,用于实现隔离性。MVCC机制会为每个事务创建一个独立的版本视图,该视图包含了当前事务能看到的所有数据版本。当其他事务对同一数据进行修改时,InnoDB会创建一个新的数据版本,并将新版本的事务ID和操作类型写入数据块的页头中。在事务执行过程中,InnoDB会根据事务的版本视图来决定使用哪个数据版本,从而实现了事务的隔离性。 InnoDB是一个强大的事务型存储引擎,支持ACID事务特性,可以自动管理事务,并通过锁定、事务日志、MVCC等机制来确保事务的一致性、隔离性和持久性。
自增主键和uuid的区别
多线程操作mySQl会默认加锁吗? 如何避免修改数据错误
加锁时机
多线程操作 MySQL 时,MySQL 会默认加锁以保证数据的一致性。MySQL 会根据事务的隔离级别自动加锁,在默认隔离级别下(REPEATABLE READ),MySQL 会对所有涉及到修改数据的操作进行加锁,包括 SELECT、INSERT、UPDATE 和 DELETE 等语句。在事务中执行修改操作时,MySQL 会对修改的行进行排它锁,其他事务不能对该行进行修改直到当前事务提交或回滚。
加的是行锁
需要注意的是,MySQL 的锁机制是基于行的锁定(Row-level locking),这意味着锁定的粒度是行而非整个表,这样可以提高并发性能。同时,MySQL 也支持表级锁定和页面锁定等不同的锁定粒度,可以根据具体情况选择不同的锁定方式。
隔离级别
MySQL 的默认隔离级别为可重复读(REPEATABLE READ),这意味着在一个事务中,读取的数据不会受到其他并发事务的修改影响,因此可以避免读取到脏数据的问题。
避免修改错误,使用一下方式
使用事务
所有对同一数据的操作放在一个事务中,事务中的操作会自动加锁,可以避免并发修改导致的数据错误问题。
使用锁
悲观锁
在需要修改数据时,先对数据进行加锁,直到完成操作后再释放锁。悲观锁的实现方式有行级锁和表级锁,可以根据具体情况选择不同的锁定粒度
乐观锁
在读取数据时,获取数据的版本号,之后在修改数据时检查版本号是否匹配,如果匹配则进行修改,否则认为数据已经被其他线程修改,需要回滚操作并重新尝试。
分布式锁
在分布式环境下,可以使用分布式锁来保证对共享资源的互斥访问,从而避免并发修改导致的数据错误问题。
不同的数据库,不在一个服务中的事务
%加载哪里索引不会生效
在 MySQL 中,模糊查询的操作符 % 用于匹配零个或多个字符,通常用于在字符串类型的字段上进行模糊匹配。由于模糊查询会导致全表扫描的情况,因此如果不加索引,查询的效率会比较低。但是,如果将 % 加在索引字段的开头,索引也不会生效,因为 MySQL 只能利用索引中的有序信息来加速查询,而将 % 加在开头则无法利用索引的有序信息。
因此,在进行模糊查询时,应当避免在索引字段的开头使用 %,可以将 % 放在查询条件的末尾,或者使用全文索引等其他技术来提高查询效率。如果需要对一个大表进行模糊查询,并且需要加速查询的速度,可以考虑使用分词技术和搜索引擎等技术来提高查询的效率。
二级缓存
Query Cache
Query Cache:Query Cache是MySQL自带的缓存机制,可以缓存查询语句的结果集。当一个查询语句被缓存后,下一次执行相同的查询语句时,MySQL会直接从缓存中返回结果,而不必再次执行查询。但是Query Cache有一些限制,比如只能缓存SELECT语句,不能缓存包含函数、变量或子查询的语句等。
External Cache
External Cache:External Cache是一种第三方缓存机制,比如Memcached、Redis等。它可以缓存更多类型的数据,比如查询结果、对象、页面等。当一个查询语句被缓存后,下一次执行相同的查询语句时,MySQL会先从外部缓存中查询结果,如果找到了就直接返回,如果没有找到就再次执行查询并将结果缓存到外部缓存中。
怎么解决幻读
mvcc
详细说明
临检锁
es
es的作用
用es主要是做数据检索用的,他的检索速度比mysql快
快的原因就是它
使用了倒排索引技术,具体来说就是
将每个文档的数据利用算法分词,得到一个个词条
对查询的数据也进行分词,然后依据词条到索引库匹配词条,获取文档id,然后依据文档id查数据
mysql正好相反,分两种情况,如果是有索引,并且依据字段整体的内容查询数据,查询速度比较快,如果没有索引,或者查的是字段内的数据,会在数据库中一条一条的查数据
es如何与mysql数据同步
上线前
全量同步
上线后
每间隔一一定时间,获取每天凌晨,做一个全量同步,防止数据不一致
增量同步
同步双写
在修改数据库中数据后对es进行修改,最不推荐的方式,耦合度最高
异步通知
在修改完数据库中的数据后,发送一个异步消息,让其他监这个队列的微服务进行修改
监听binlog
- 给mysql开启binlog功能 - mysql完成增、删、改操作都会记录在binlog中 es基于canal监听binlog变化,实时更新elasticsearch中的内容
我们以应用最广泛的 canal 为例,canal 通过canal-adapter,支持多种适配器,其中就有 ES 适配器,通过一些配置,启动之后,就可以直接把 MySQL 数据同步到 ES,这个过程是零代码的。 canal 模拟 MySQL slave 的交互协议,伪装自己为 MySQL slave ,向 MySQL master 发送dump 协议 MySQL master 收到 dump 请求,开始推送 binary log 给 slave (即 canal ) canal 解析 binary log 对象(原始为 byte 流)