1-ssm

本文详细探讨了MySQL的索引结构,强调了B+树作为索引的优势,包括其在处理范围查询和数据遍历方面的高效性。还介绍了MySQL的各种索引类型,如主键索引、唯一索引、普通索引等。接着,讨论了事务隔离级别,分析了读已提交(Read Committed)和可重复读(Repeatable Read)的选择理由。此外,文章涵盖了Spring框架的注解使用、Spring Boot的核心注解、Spring MVC的工作流程以及Spring事务管理。最后,讲解了MyBatis的执行流程和#{}与${}的区别,以及Web开发中的前端请求流程、HTTP协议、动静分离和前后端分离的概念。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

spring

Spring 是如何解决循环依赖问题的?

我们都知道,如果在代码中,将两个或多个 Bean 互相之间持有对方的引用就会发生循环依赖。循环的依赖将会导致注入死循环。这是 Spring 发生循环依赖的原因。
循环依赖有三种形态:

第一种互相依赖:A 依赖 B,B 又依赖 A,它们之间形成了循环依赖。
第二种三者间依赖:A 依赖 B,B 依赖 C,C 又依赖 A,形成了循环依赖。
第三种是自我依赖:A 依赖 A 形成了循环依赖。

而 Spring 中设计了三级缓存来解决循环依赖问题,当我们去调用 getBean()方法的时候,Spring 会先从一级缓存中去找到目标 Bean,如果发现一级缓存中没有便会去二级缓存中去找,而如果一、二级缓存中都没有找到,意味着该目标 Bean还没有实例化。于是,Spring 容器会实例化目标 Bean(PS:刚初始化的 Bean称为早期 Bean) 。然后,将目标 Bean 放入到二级缓存中,同时,加上标记是否存在循环依赖。如果不存在循环依赖便会将目标 Bean 存入到二级缓存,否则,便会标记该 Bean 存在循环依赖,然后将等待下一次轮询赋值,也就是解析@Autowired 注解。等@Autowired 注解赋值完成后(PS:完成赋值的 Bean 称为成熟 Bean) ,会将目标 Bean 存入到一级缓存。

Spring 一级缓存中存放所有的成熟 Bean,
二级缓存中存放所有的早期 Bean,先取一级缓存,再去二级缓存。
面试官:那么,前面有提到三级缓存,三级缓存的作用是什么?
三级缓存是用来存储代理 Bean,当调用 getBean()方法时,发现目标 Bean 需要通过代理工厂来创建,此时会将创建好的实例保存到三级缓存,最终也会将赋值好的 Bean 同步到一级缓存中。
面试官:Spring 中哪些情况下,不能解决循环依赖问题?
1.多例 Bean 通过 setter 注入的情况,不能解决循环依赖问题
2.构造器注入的 Bean 的情况,不能解决循环依赖问题
3.单例的代理 Bean 通过 Setter 注入的情况,不能解决循环依赖问题
4.设置了@DependsOn 的 Bean 的情况,不能解决循环依赖问题

SpringBean 的作用域有哪些?

首先呢,Spring 框架里面的 IOC 容器,可以非常方便的去帮助我们管理应用里面的 Bean 对象实例。
我们只需要按照 Spring 里面提供的 xml 或者注解等方式去告诉 IOC 容器,哪些Bean 需要被 IOC 容器管理就行了。
其次呢,既然是 Bean 对象实例的管理,那意味着这些实例,是存在生命周期,也就是所谓的作用域。
理论上来说,常规的生命周期只有两种:
singleton,也就是单例,意味着在整个 Spring 容器中只会存在一个 Bean 实例。
prototype,翻译成原型,意味着每次从 IOC 容器去获取指定 Bean 的时候,都会返回一个新的实例对象。
但是在基于 Spring 框架下的 Web 应用里面,增加了一个会话纬度来控制 Bean的生命周期,主要有三个选择
request,针对每一次 http 请求,都会创建一个新的 Bean
session,以 sesssion 会话为纬度,同一个 session 共享同一个 Bean 实例,不同的 session 产生不同的 Bean 实例
globalSession,针对全局 session 纬度,共享同一个 Bean 实例

介绍下 Spring IoC 的工作流程

好的,这个问题我会从几个方面来回答。
IOC 是什么
Bean 的声明方式
IOC 的工作流程

IOC 的全称是 Inversion Of Control,也就是控制反转,它的核心思想是把对象的管理权限交给容器。
应用程序如果需要使用到某个对象实例,直接从 IOC 容器中去获取就行,这样设计的好处是降低了程序里面对象与对象之间的耦合性。使得程序的整个体系结构变得更加灵活。

Spring 里面很多方式去定义 Bean, 比如 XML 里面的标签、@Service、@Component、@Repository、@Configuration 配置类中的@Bean 注解等等。
Spring 在启动的时候,会去解析这些 Bean 然后保存到 IOC 容器里面。

Spring IOC 的工作流程大致可以分为两个阶段。
第一个阶段,就是 IOC 容器的初始化
这个阶段主要是根据程序中定义的 XML 或者注解等 Bean 的声明方式
通过解析和加载后生成 BeanDefinition,然后把 BeanDefinition 注册到 IOC 容器。
通过注解或者 xml 声明的 bean 都会解析得到一个 BeanDefinition 实体,实体中包含这个 bean 中定义的基本属性。
最后把这个 BeanDefinition 保存到一个 Map 集合里面,从而完成了 IOC 的初始化。
IoC 容器的作用就是对这些注册的 Bean 的定义信息进行处理和维护,它 IoC 容器控制反转的核心。
第二个阶段,完成 Bean 初始化及依赖注入,然后进入到第二个阶段,这个阶段会做两个事情
通过反射针对没有设置 lazy-init 属性的单例 bean 进行初始化。完成 Bean 的依赖注入。
第三个阶段,Bean 的使用
通常我们会通过@Autowired 或者BeanFactory.getBean()从IOC 容器中获取指定的 bean 实例。
另外,针对设置 layy-init 属性以及非单例 bean 的实例化,是在每次获取 bean对象的时候,调用 bean 的初始化方法来完成实例化的,并且 Spring IOC 容器不会去管理这些 Bean。

Spring 中事务的传播行为有哪些?

首选,所谓的事务传播行为,就是多个声明了事务的方法相互调用的时候,这个事务应该如何传播。
比如说,methodA()调用 methodB(),两个方法都显示的开启了事务。
那么 methodB()是开启一个新事务,还是继续在 methodA()这个事务中执行?就取 决 于 事 务 的 传 播 行 为 。
在 Spring 中,定义了 7 种事务传播行为。
REQUIRED:默认的 Spring 事物传播级别,如果当前存在事务,则加入这个事务,如果不存在事务,就新建一个事务。
REQUIRE_NEW:不管是否存在事务,都会新开一个事务,新老事务相互独立。外部事务抛出异常回滚不会影响内部事务的正常提交。
NESTED:如果当前存在事务,则嵌套在当前事务中执行。如果当前没有事务,则新建一个事务,类似于 REQUIRE_NEW。
SUPPORTS:表示支持当前事务,如果当前不存在事务,以非事务的方式执行。
NOT_SUPPORTED:表示以非事务的方式来运行,如果当前存在事务,则把当前事务挂起。
MANDATORY:强制事务执行,若当前不存在事务,则抛出异常. NEVER:以非事务的方式执行,如果当前存在事务,则抛出异常。
Spring 事务传播级别一般不需要定义,默认就是 PROPAGATION_REQUIRED,除非在嵌套事务的情况下需要重点了解。

请解释下 Spring 框架中的 IoC?

IoC(控制反转)是一种设计思想,就是将原本在程序中手动创建对象的控制权,交由spring框架来管理。IoC容器是spring用来实现ioc的载体,IoC容器实际上就是map,map存放的就是各种对象。

将对象之间相互依赖的关系交给IoC容器来管理,并由IoC容器完成对象的注入,这样可以很大程度上简化应用的开发,把应用从复杂的依赖关系 中解放出来。IoC容器就像一个工厂,当我们需要创建一个对象的时候,只需要配置好配置文件/注解即可,完成不用考虑对象是如何让创建的。

IOC:把对象的创建、初始化、销毁交给 spring 来管理,而不是由开发者控制,实现控制反转。

依赖注入DI ,就是由IoC容器在运行期间,动态地将某种依赖关系注入到对象之中。

依赖注入(DI)和控制反转(IoC)是从不同的角度描述的同一件事情,就是指通过引入IoC容器,利用依赖关系注入的方式,实现对象之间的解耦。

spring 常用的注入方式有哪些?

构造方法注入

setter注入

基于注解的注入

解释一下什么是 aop?

AOP(Aspect-Oriented Programming,面向方面编程),可以说是OOP(Object-Oriented Programing,面向对象编程)的补充和完善。OOP引入封装、继承和多态性等概念来建立一种对象层次结构,用以模拟公共行为的一个集合。当我们需要为分散的对象引入公共行为的时候,OOP则显得无能为力。也就是说,OOP允许你定义从上到下的关系,但并不适合定义从左到右的关系。例如日志功能。日志代码往往水平地散布在所有对象层次中,而与它所散布到的对象的核心功能毫无关系。对于其他类型的代码,如安全性、异常处理和透明的持续性也是如此。这种散布在各处的无关的代码被称为横切(cross-cutting)代码,在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。

而AOP技术则恰恰相反,它利用一种称为“横切”的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其名为“Aspect”,即方面。所谓“方面”,简单地说,就是将那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性。AOP代表的是一个横向的关系,如果说“对象”是一个空心的圆柱体,其中封装的是对象的属性和行为;那么面向方面编程的方法,就仿佛一把利刃,将这些空心圆柱体剖开,以获得其内部的消息。而剖开的切面,也就是所谓的“方面”了。然后它又以巧夺天功的妙手将这些剖开的切面复原,不留痕迹。

使用“横切”技术,AOP把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处都基本相似。比如权限认证、日志、事务处理。Aop 的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。正如Avanade公司的高级方案构架师Adam Magee所说,AOP的核心思想就是“将应用程序中的商业逻辑同对其提供支持的通用服务进行分离。”

spring 自动装配 bean 有哪些方式?

隐式的bean发现机制和自动装配

在java代码或者XML中进行显示配置

说一下 spring 的事务隔离?

事务隔离级别指的是一个事务对数据的修改与另一个并行的事务的隔离程度,当多个事务同时访问相同数据时,如果没有采取必要的隔离机制,就可能发生以下问题:

脏读:一个事务读到另一个事务未提交的更新数据。

幻读:例如第一个事务对一个表中的数据进行了修改,比如这种修改涉及到表中的“全部数据行”。同时,第二个事务也修改这个表中的数据,这种修改是向表中插入“一行新数据”。那么,以后就会发生操作第一个事务的用户发现表中还存在没有修改的数据行,就好象发生了幻觉一样。

不可重复读:比方说在同一个事务中先后执行两条一模一样的select语句,期间在此次事务中没有执行过任何DDL语句,但先后得到的结果不一致,这就是不可重复读。

spring常用的注解*

  • 声明bean的注解

    @Component:泛指各种组件 ------- 以下三个都可以称为@Component。

    @Controller:控制层

    @Service:业务层

    @Repository:数据访问层

  • 注入bean的注解

    @Resource:先按照形参的名称进行注入,找不到再按类型查找bean注入(常用)

    @Autowired:先按照形参的类型进行注入,再按照名称查找

  • Java配置类相关注解

    @Configuration:声明当前类为配置类;

    @ComponentScan:用于对Component进行扫描;

    @PropertySource :把properties配置数据加载的spring容器,通过@Value来获取数据

    @Value:即别的类里也可以通过@Value来获取数据(就是把properties数据对象放进了容器里)

    @Bean:注解在方法上,声明当前方法的返回值为一个bean,替代xml中的方式;

  • 切面(AOP)相关注解

    @Aspect 声明一个切面

    @After 在方法执行之后执行(方法上)

    @Before 在方法执行之前执行(方法上)

    @Around 在方法执行之前与之后执行(方法上)

    @PointCut 声明切点

Spring MVC常用的注解有哪些?*

  • url映射:
    @RequestMapping注解用来做url映射,把url映射一个处理器方法上。可以限定客户端提交请求的方法。

    @GetMapping,@PostMapping,@PutMapping,@DeleteMapping (方法)

  • 接收参数:
    @RequestParam,针对key=value格式的参数,注解到简单类型的形参上(String,Date,包装类,基本类型),可以设置默认值。

    @DateTimeFormated:指定日期格式

    @RequestBody,针对{key:value}json格式的参数,注解到对象类型的形参上。也可以注解到Map上。

    @PathVariable:用于接收路径参数,比如@RequestMapping(“/hello/{name}”)声明的路径,将注解放在参数前,即可获取该值,通常作为Restful的接口实现方法

  • 返回数据:
    @ResponseBody,表示处理器方法返回值不执行视图解析器,而是执行jackson消息转换器把返回值对象转 json字符串。

  • 异常相关

    @ControllerAdvice:全局异常处理/全局数据绑定/全局数据预处理 ControllerAdvice的常用场景

    @ExceptionHandler:用于全局处理控制器里的异常。

Spring Boot 的核心注解是哪个?它主要由哪几个注解组成的?*

启动类上面的注解是@SpringBootApplication,它也是 Spring Boot 的核心注解,主要组合包含了以下 3 个注解:

@SpringBootConfiguration:组合了 @Configuration 注解,实现配置文件的功能。

@EnableAutoConfiguration:打开自动配置的功能,也可以关闭某个自动配置的选项,如关闭数据源自动配置功能: @SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })。

@ComponentScan:Spring组件扫描。

springmvc

说说你对 Spring MVC 的理解

好的,关于这个问题,我会从几个方面来回答。
首先,Spring MVC 是是属于 Spring Framework 生态里面的一个模块,它是在Servlet 基础上构建并且使用 MVC 模式设计的一个 Web 框架,主要的目的是简化传统 Servlet+JSP 模式下的 Web 开发方式。
其次,Spring MVC 的整体架构设计对 Java Web 里面的 MVC 架构模式做了增强和扩展,主要有几个方面。
把传统 MVC 框架里面的 Controller 控制器做了拆分, 分成了前端控制器DispatcherServlet 和后端控制器 Controller。
把 Model 模型拆分成业务层 Service 和数据访问层 Repository。
在视图层,可以支持不同的视图,比如 Freemark、velocity、JSP 等等。
所以,Spring MVC 天生就是为了 MVC 模式而设计的,因此在开发 MVC 应用的时候会更加方便和灵活。
Spring MVC 的具体工作流程是,浏览器的请求首先会经过 SpringMVC 里面的核心控制器 DispatcherServlet,它负责对请求进行分发到对应的 Controller。
Controller 里面处理完业务逻辑之后,返回 ModeAndView。
然后 DispatcherServlet 寻找一个或者多个 ViewResolver 视图解析器,找到ModeAndView 指定的视图,并把数据显示到客户端。

在这里插入图片描述

SpingMvc 中的控制器的注解一般用那个,有没有别的注解可以替代?

答:一般用@Conntroller 注解,表示是表现层,不能用用别的注解代替。

如果在拦截请求中,我想拦截 get 方式提交的方法,怎么配置?

答:可以在@RequestMapping 注解里面加上 method=RequestMethod.GET

怎么样在方法里面得到 Request,或者 Session?

答:直接在方法的形参中声明 request,SpringMvc 就自动把 request 对象传入

我想在拦截的方法里面得到从前台传入的参数,怎么得到?

答:直接在形参里面声明这个参数就可以,但必须名字和传过来的参数一样

如果前台有很多个参数传入,并且这些参数都是一个对象的,那么怎么样快速得到这个对 象?

答:直接在方法中声明这个对象,SpringMvc 就自动会把属性赋值到这个对象里面。

SpringMvc 中函数的返回值是什么?

答:返回值可以有很多类型,有 String, ModelAndView,当一般用 String 比较好。

SpringMvc 用什么对象从后台向前台传递数据的?

答:通过 ModelMap 对象,可以在这个对象里面用 put 方法,把对象加到里面,前台就可以通 过 el 表达式拿到。

怎么样把 ModelMap 里面的数据放入 Session 里面?

答:可以在类上面加上@SessionAttributes 注解,里面包含的字符串就是要放入 session 里面 的 key

SpringMvc 怎么和 AJAX 相互调用的?

答: 通过 Jackson 框架就可以把 Java 里面的对象直接转化成 Js 可以识别的 Json 对象。

具体步骤如下 :

1)加入 Jackson.jar

2)在配置文件中配置 json 的映射

3)在接受 Ajax 方法里面可以直接返回 Object,List 等,但方法前面要加上@ResponseBody 注解

当一个方法向 AJAX 返回特殊对象,譬如 Object,List 等,需要做什么处理?

答:要加上@ResponseBody 注解

mybatis

mybatis的执行流程

首先通过Resources加载配置好的mybatis.xml配置文件

  • 第一步通过SqlSessionFactoryBuilder创建SqlSessionFactory:

    首先在SqlSessionFactoryBuilder的build()方法中可以看到MyBatis内部定义了一个类XMLConfigBuilder用来解析配置文件mybatis-config.xml。针对配置文件中的每一个节点进行解析并将数据存放到Configuration这个对象中,紧接着使用带有Configuration的构造方法发返回一个DefautSqlSessionFactory。

  • 第二步通过SqlSessionFactory创建SqlSession:

  • 第三步通过SqlSession拿到Mapper接口的代理对象

  • 第四步通过MapperProxy的invoke方法调用Maper中相应的方法:

mybatis 中 #{}和 ${}的区别是什么?

#{}是预编译处理,${}是字符串替换;

Mybatis在处理#{}时,会将sql中的#{}替换为?号,调用PreparedStatement的set方法来赋值;

Mybatis在处理时,就是把{}时,就是把时,就是把{}替换成变量的值;

使用#{}可以有效的防止SQL注入,提高系统安全性。

mybatis 有几种分页方式?

数组分页

sql分页

拦截器分页

RowBounds分页

mybatis 逻辑分页和物理分页的区别是什么?

物理分页速度上并不一定快于逻辑分页,逻辑分页速度上也并不一定快于物理分页。

物理分页总是优于逻辑分页:没有必要将属于数据库端的压力加诸到应用端来,就算速度上存在优势,然而其它性能上的优点足以弥补这个缺点。

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,几乎所有的包括Hibernate,支持延迟加载的原理都是一样的。

说一下 mybatis 的一级缓存和二级缓存?

一级缓存: 基于 PerpetualCache 的 HashMap 本地缓存,其存储作用域为 Session,当 Session flush 或 close 之后,该 Session 中的所有 Cache 就将清空,默认打开一级缓存。

二级缓存与一级缓存其机制相同,默认也是采用 PerpetualCache,HashMap 存储,不同在于其存储作用域为 Mapper(Namespace),并且可自定义存储源,如 Ehcache。默认不打开二级缓存,要开启二级缓存,使用二级缓存属性类需要实现Serializable序列化接口(可用来保存对象的状态),可在它的映射文件中配置 ;

对于缓存数据更新机制,当某一个作用域(一级缓存 Session/二级缓存Namespaces)的进行了C/U/D 操作后,默认该作用域下所有 select 中的缓存将被 clear

mybatis 分页插件的实现原理是什么?

分页插件的基本原理是使用Mybatis提供的插件接口,实现自定义插件,在插件的拦截方法内拦截待执行的sql,然后重写sql,根据dialect方言,添加对应的物理分页语句和物理分页参数。

SpringCloud

关于“你对 Spring Cloud 的理解”

Spring Cloud 是一套分布式微服务的技术解决方案 它提供了快速构建分布式系统的常用的一些组件比如说配置管理、服务的注册与发现、服务调用的负载均衡、资源隔离、熔断降级等等不过 Spring Cloud 只是 Spring 官方提供的一套微服务标准定义而真正的实现
目前有两套体系用的比较
一个是 Spring Cloud Netflix
一个是 Spring Cloud Alibaba
Spring Cloud Netflix 是基于 Netflix 这个公司的开源组件集成的一套微服务解决方案,其中的组件有
1.Ribbon——负载均衡
2.Hystrix——服务熔断
3.Zuul——网关
4.Eureka——服务注册与发现
5.Feign——服务调用
Spring Cloud Alibaba 是基于阿里巴巴开源组件集成的一套微服务解决方案,其中包括
1.Dubbo——消息通讯
2.Nacos——服务注册与发现
3.Seata——事务隔离
4.Sentinel——熔断降级有了
Spring Cloud 这样的技术生态使得我们在落地微服务架构时

不用去考虑第三方技术集成带来额外成本 只要通过配置组件来完成架构下的技术问题从而可以让我们更加侧重性能方面
以上这些就是我对 Spring Cloud 的个人理解!

Dubbo 的服务请求失败怎么处理?

Dubbo 是一个 RPC 框架,它为我们的应用提供了远程通信能力的封装,同时, Dubbo 在 RPC 通信的基础上,逐步在向一个生态在演进,它涵盖了服务注册、动态路由、容错、服务降级、负载均衡等能力,基本上在微服务架构下面临的问题,Dubbo 都可以解决。

而对于 Dubbo 服务请求失败的场景,默认提供了重试的容错机制,也就是说,如果基于 Dubbo 进行服务间通信出现异常,服务消费者会对服务提供者集群中其他的节点发起重试,确保这次请求成功,默认的额外重试次数是 2 次。

除此之外,Dubbo 还提供了更多的容错策略,我们可以根据不同的业务场景来进行选择。

快速失败策略,服务消费者只发起一次请求,如果请求失败,就直接把错误抛出去。这种比较适合在非幂等性场景中使用

失败安全策略,如果出现服务通信异常,直接把这个异常吞掉不做任何处理

失败自动恢复策略,后台记录失败请求,然后通过定时任务来对这个失败的请求进行重发。并行调用多个服务策略,就是把这个消息广播给服务提供者集群,只要有任何一个节点返回,就表示请求执行成功。

广播调用策略,逐个调用服务提供者集群,只要集群中任何一个节点出现异常,就表示本次请求失败

要注意的是,默认基于重试策略的容错机制中,需要注意幂等性的处理,否则在事务型的操作中,容易出现多次数据变更的问题。

Dubbo 是如何动态感知服务下线的?

好的,面试官,关于这个问题,我从几个方面来回答。
首先,Dubbo 默认采用 Zookeeper 实现服务的注册与服务发现,简单来说啊,就是多个 Dubbo 服务之间的通信地址,是使用 Zookeeper 来维护的。
在 Zookeeper 上,会采用树形结构的方式来维护 Dubbo 服务提供端的协议地址,Dubbo 服务消费端会从 Zookeeper Server 上去查找目标服务的地址列表,从而完成服务的注册和消费的功能。
Zookeeper 会通过心跳检测机制,来判断 Dubbo 服务提供端的运行状态,来决定是否应该把这个服务从地址列表剔除。
当 Dubbo 服务提供方出现故障导致 Zookeeper 剔除了这个服务的地址,那么 Dubbo 服务消费端需要感知到地址的变化,从而避免后续的请求发送到故障节点,导致请求失败。
也就是说 Dubbo 要提供服务下线的动态感知能力。这个能力是通过 Zookeeper 里面提供的 Watch 机制来实现的,简单来说呢, Dubbo 服务消费端会使用 Zookeeper 里面的 Watch 来针对Zookeeper Server 端的/providers 节点注册监听,
一旦这个节点下的子节点发生变化,Zookeeper Server 就会发送一个事件通知Dubbo Client 端.Dubbo Client 端收到事件以后,就会把本地缓存的这个服务地址删除,这样后续就不会把请求发送到失败的节点上,完成服务下线感知。

Zookeeper 中的 Watch 机制的原理?

好的,这个问题我打算从两个方面来回答。
Zookeeper 是一个分布式协调组件,为分布式架构下的多个应用组件提供了顺序访问控制能力。
它的数据存储采用了类似于文件系统的树形结构,以节点的方式来管理存储在Zookeeper 上的数据。
Zookeeper 提供了一个 Watch 机制,可以让客户端感知到 Zookeeper Server上存储的数据变化,这样一种机制可以让 Zookeeper 实现很多的场景,比如配置中心、注册中心等。
Watch 机制采用了 Push 的方式来实现,也就是说客户端和 Zookeeper Server会建立一个长连接,一旦监听的指定节点发生了变化,就会通过这个长连接把变化的事件推送给客户端。
Watch 的具体流程分为几个部分:
首先,是客户端通过指定命令比如 exists、get,对特定路径增加 watch
然后服务端收到请求以后,用 HashMap 保存这个客户端会话以及对应关注的节点路径,同时客户端也会使用 HashMap
存储指定节点和事件回调函数的对应关系。
当服务端指定被 watch 的节点发生变化后,就会找到这个节点对应的会话,把变化的事件和节点信息发给这个客户端。
客户端收到请求以后,从 ZkWatcherManager 里面对应的回调方法进行调用,完成事件变更的通知。

Zookeeper 如何实现 Leader 选举

首先,Zookeeper 集群节点由三种角色组成,分别是
Leader,负责所有事务请求的处理,以及过半提交的投票发起和决策。
Follower,负责接收客户端的非事务请求,而事务请求会转发给 Leader 节点来处理, 另外,Follower 节点还会参与 Leader 选举的投票。
Observer,负责接收客户端的非事务请求,事务请求会转发给 Leader 节点来处理,另外 Observer 节点不参与任何投票,只是为了扩展 Zookeeper 集群来分担读操作的压力。

其次,Zookeeper 集群是一种典型的中心化架构,也就是会有一个 Leader 作为决策节点,专门负责事务请求的处理和数据的同步。
这种架构的好处是可以减少集群架构里面数据同步的复杂度,集群管理会更加简单和稳定。
但是,会带来 Leader 选举的一个问题,也就是说,如果 Leader 节点宕机了,为了保证集群继续提供可靠的服务,
Zookeeper 需要从剩下的 Follower 节点里面去选举一个新的节点作为 Leader,也就是所谓的 Leader 选举!

具体的实现是,每一个节点都会向集群里面的其他节点发送一个票据 Vote,这个票据包括三个属性。
epoch, 逻辑时钟,用来表示当前票据是否过期。
zxid,事务 id,表示当前节点最新存储的数据的事务编号。
myid,服务器 id,在 myid 文件里面填写的数字。

每个节点都会选自己当 Leader,所以第一次投票的时候携带的是当前节点的信息。
接下来每个节点用收到的票据和自己节点的票据做比较,根据 epoch、zxid、myid的顺序逐一比较,以值最大的一方获胜。比较结束以后这个节点下次再投票的时候,发送的投票请求就是获胜的 Vote 信息。
然后通过多轮投票以后,每个节点都会去统计当前达成一致的票据,以少数服从多数的方式,最终获得票据最多的节点成为 Leader。
以上就是我对这个问题的理解。

最后我再补充一下,选择 epoch/zxid/myid 作为投票评判依据的原因,我是这么理解的。
epoch ,因为网络通信延迟的可能性,有可能在新一轮的投票里面收到上一轮投票的票据,这种数据应该丢弃,否则会影响投票的结果和效率。
zxid, zxid 越大,说明这个节点的数据越接近 leader,所以用 zxid 做判断条件是为了避免数据丢失的问题。
myid, 服务器 id,这个是避免投票时间过长,直接用 myid 最大值作为快速终结投票的属性。

Nacos 配置更新的工作流程

好的,面试官,这个问题我需要从几个方面来回答。

首先,Nacos 是采用长轮训的方式向 Nacos Server 端发起配置更新查询的功能。
所谓长轮训就是客户端发起一次轮训请求到服务端,当服务端配置没有任何变更的时候,这个连接一直打开。
直到服务端有配置或者连接超时后返回。

Nacos Client 端需要获取服务端变更的配置,前提是要有一个比较,也就是拿客户端本地的配置信息和服务端的配置信息进行比较。
一旦发现和服务端的配置有差异,就表示服务端配置有更新,于是把更新的配置拉到本地。
在这个过程中,有可能因为客户端配置比较多,导致比较的时间较长,使得配置同步较慢的问题。
于是 Nacos 针对这个场景,做了两个方面的优化。
减少网络通信的数据量,客户端把需要进行比较的配置进行分片,每一个分片大小是 3000,也就是说,每次最多拿 3000 个配置去 Nacos Server 端进行比较。
分阶段进行比较和更新;
第一阶段,客户端把这 3000 个配置的 key 以及对应的 value 值的 md5 拼接成一个字符串,然后发送到 Nacos Server 端进行判断,服务端会逐个比较这些配置中 md5 不同的 key,把存在更新的 key 返回给客户端。
第二阶段,客户端拿到这些变更的 key,循环逐个去调用服务单获取这些 key 的value 值。
这两个优化,核心目的是减少网络通信数据包的大小,把一次大的数据包通信拆分成了多次小的数据包通信。
虽然会增加网络通信次数,但是对整体的性能有较大的提升。
最后,再采用长连接这种方式,既减少了 pull 轮询次数,又利用了长连接的优势,很好的实现了配置的动态更新同步功能。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值