跳转到主要内容

面试题库

JavaWeb 框架与微服务面试题

JavaWeb、Spring、MyBatis、Redis、MQ、ElasticSearch、微服务、网络、工具,共 405 题。

  • Java
  • Spring
  • 微服务
  • 面试

JavaWeb 框架与微服务面试题

来源:resource/御码IT教育-Java+Python智能体-JavaWeb框架+微服务.pdf

一、JavaWeb

1、基础概念

1、什么是 JavaWeb

  1. 基于HTTP 协议、请求 / 响应模型
  2. 采用B/S 架构(浏览器 / 服务器)
  3. 核心技术:Servlet、JSP、Filter、Listener
  4. 运行在Web 服务器(如 Tomcat)中

2、Web 开发的两种架构

B/S 架构:Browser / Server(浏览器 / 服务器) C/S 架构:Client / Server(客户端 / 服务器)

3、HTTP 是无状态的,什么意思

HTTP 无状态:服务器不记住你,每次请求都当第一次见。 无状态:协议本身不会保存客户端的任何信息、身份、历史请求。 每一次请求都是独立、互不关联的。 正因为无状态,才需要Cookie、Session、Token来做会话跟踪,记住用户。

2、HTTP 协议

1、HTTP 请求报文结构

请求行:请求方式 + URL + 协议版本

例:GET /index.html HTTP/1.1

请求头:键值对形式,如 Host、User-Agent、Content-Type 等 空行:必须有,用来分隔请求头和请求体 请求体:只有 POST、PUT 等才有,存放提交的参数 / 数据

对比维度

GET

POST

参数位置

在 URL 后面 在请求体中

安全性

低(明文可见) 相对高

数据大小

有限制(浏览器限制) 无限制

数据类型

只支持 ASCII 字符 支持多种类型(文件、对象)

缓存

会被浏览器缓存 一般不缓存

书签 / 历史

可收藏、可保留历史 不可以

幂等性

幂等(查询) 非幂等(提交 / 修改)

用途

查询、获取数据 提交、新增、修改、上传

2、HTTP 响应报文结构

响应行:协议版本 + 状态码 + 状态描述

例:HTTP/1.1 200 OK

响应头:键值对,如 Content-Type、Content-Length、Set-Cookie 等 空行:分隔响应头和响应体 响应体:服务器返回给浏览器的内容:HTML、JSON、图片等

3、常见状态码

200:成功 302:重定向 304:缓存 400:参数错误 401:未认证 403:禁止 404:不存在 500:服务器错误 502:网关错误 504:超时

4、GET 和 POST 区别

对比维度

HTTP

HTTPS

安全性 明文传输,不安全 加密传输,安全 加密方式 无 SSL/TLS 加密 默认端口

80

443

响应速度 快 稍慢(加密解密开销) 证书 不需要 需要 CA 证书 用途 普通网页 登录、支付、隐私数据

5、HTTP 和 HTTPS 区别

3、Tomcat

1、Tomcat 是什么

Tomcat 是 Apache 开源的、轻量级 Web 服务器,也是 Servlet 容器。

作用: 运行JavaWeb应用 处理客户端请求,执行Servlet/JSP 响应结果返回给浏览器

2、Tomcat 顶层架构

Tomcat 顶层由以下几个核心组件组成:

  1. Server:整个 Tomcat 服务器实例,是最顶层组件。
  2. Service:一个 Server 可以包含多个 Service,用于封装Connector和Engine。
  3. Connector:连接器,负责接收请求、响应结果,处理网络通信。
  4. Container:容器,内部层级:

Engine → Host → Context → Wrapper

Engine:引擎,接收请求并分发到对应 Host Host:虚拟主机 Context:一个 Web 应用 Wrapper:对应一个 Servlet

模式

全称

核心原理

线程

模型

性能

默认版本

适用场景

BIO

Blocking I/O(阻塞 I/O) 传统阻塞 I/O,一个 连接对应一个线程 一连 接一 线程 低,高 并发下 线程爆 炸 Tomcat 7 及以下 低并发、 简单应用

NIO

Non- blocking I/O(非阻 塞 I/O) Java NIO,多路复用 (Selector),少量 线程管理大量连接 多路 复 用, 线程 池 中高, 资源利 用率高 Tomcat 8+ 默认 高并发 Web 应用 (主流)

APR

Apache Portable Runtime 调用操作系统原生 I/O(如 epoll/IOCP),零拷 贝 操作 系统 级异 步 最高, 静态资 源性能 极佳 Windows 默认, Linux 需 安装 生产环 境、高并 发、静态 资源多

3、Tomcat 有哪几种运行模式

BIO

阻塞式,I/O 等待时线程挂起,高并发下线程数激增,资源消耗大。 Tomcat 7 及以下默认,Tomcat 8.5+ 已弃用。

NIO

非阻塞 + 多路复用,一个线程可管理成千上万个连接。 Tomcat 8+ 默认,兼顾性能与易用性,是目前主流选择。

APR

性能天花板,直接调用系统底层 I/O,支持零拷贝、原生 SSL。

Linux 需安装apr 、apr-util 、tomcat-native库。

4、Tomcat 如何处理一个请求

  1. 客户端发送请求,到达 Tomcat 的Connector(连接器)。
  2. Connector接收请求,交给Engine(引擎)。
  3. Engine根据域名找到对应的Host(虚拟主机)。
  4. Host根据路径找到对应的Context(Web 应用)。
  5. Context找到对应的Wrapper(Servlet 包装器)。
  6. Wrapper找到并执行目标Servlet。
  7. Servlet 处理业务,生成响应。
  8. 响应原路返回给客户端。

4、Servlet

1、什么是 Servlet

Servlet 是运行在服务器端的 Java 程序,用来接收、处理客户端请求,并返回响应。

简单说:Servlet 就是 JavaWeb 里处理请求、写业务逻辑的核心组件。

2、Servlet 生命周期

  1. 加载:类加载
  2. 实例化:构造方法
  3. 初始化:init() ,只执行一次
4. 服务:service()→doGet/doPost ,每次请求执行
  1. 销毁:destroy() ,服务器关闭执行

3、Servlet 是单例多线程吗

单例

服务器启动 / 第一次访问时,只创建一个 Servlet 实例 全局只有这一个对象

多线程

每个请求过来,Tomcat 都会用独立线程去处理 多个线程同时访问同一个 Servlet 对象

线程安全问题(必考)

不要在 Servlet 中定义成员变量(会被多线程共享,出现并发问题) 只使用局部变量,线程安全

4、Servlet 接口有哪些方法

init()
service()
destroy()
getServletConfig()
getServletInfo()

5、HttpServlet 的service做什么

  1. 接收请求
  2. 判断是GET、POST、PUT、DELETE哪种请求
  3. 自动转发到:
doGet()
doPost()
doPut()
doDelete()
  1. 我们一般不重写 service,只重写doGet / doPost

5、请求、转发、重定向

1、request作用域

request 作用域:一次请求有效,请求转发后依然有效,重定向 / 新请求就失效。

作用范围:当前请求 + 转发后的目标页面 生命周期:

  1. 客户端发起请求,创建 request
  2. 服务器处理、转发,都在同一个 request
  3. 响应回到浏览器,request 销毁 常用方法:
setAttribute("key", value)
getAttribute("key")
2、request常用方法
  1. 获取请求参数
String getParameter(String name)
String[] getParameterValues(String name)
Map<String,String[]> getParameterMap()
  1. 作用域操作
setAttribute(String name, Object value)
getAttribute(String name)
removeAttribute(String name)
  1. 获取请求信息
getRequestURI()
getRequestURL()
getMethod()
getHeader(String name)
getContextPath()
  1. 转发
getRequestDispatcher(String path).forward(request,response)

对比维度

转发 forward

重定向 redirect

Java 代码

forward
sendRedirect

地址栏

不变

变

请求次数

1 次 2 次

数据共享

共享同一个 request 不共享

目标资源

只能站内 可跨域

本质

服务器内部跳转 告诉浏览器重新请求

3、转发和重定向区别

6、会话技术:Cookie & Session

1、Cookie 是什么

客户端会话技术,数据存在浏览器。 由服务器创建,通过响应头Set-Cookie发给浏览器 存在客户端,下次请求时自动通过请求头Cookie带给服务器 大小一般≤4KB,只能存字符串 可以设置有效期,到期自动删除 主要用途:记住登录、记住用户名、会话跟踪

2、Session 是什么

服务器端会话技术,数据存在服务器,基于 Cookie(JSESSIONID)实现。 客户端第一次请求,服务器创建Session,生成唯一JSESSIONID 服务器通过Set-Cookie把 JSESSIONID 发给浏览器 浏览器保存 JSESSIONID,下次请求自动带上 服务器根据 JSESSIONID 找到对应的 Session Session 失效 / 超时,JSESSIONID 也失效

3、Session 生命周期

创建:第一次调用request.getSession()时创建

存活:在服务器内存中保存,客户端每次请求带 JSESSIONID 访问 销毁: 超时销毁(默认 30 分钟)

调用session.invalidate()手动销毁

服务器正常关闭 / 项目卸载

对比维度

Session

存储位置 客户端(浏览器) 服务器端 安全性 较低,明文 / 可篡改 较高,存在服务器 存储大小 单个 ≤ 4KB 几乎无限制 存储类型 只能存字符串 可存对象、数据 服务器压力 无压力 大量会话会占内存 生命周期 可长期保存 默认 30 分钟,依赖浏览器关闭 依赖关系 可独立使用 依赖 Cookie(JSESSIONID)

4、Cookie 和 Session 区别

Cookie:客户端,不安全,容量小 Session:服务器,安全,容量大

5、浏览器禁用 Cookie,Session 还能用吗?

浏览器禁用 Cookie 后,JSESSIONID 无法通过 Cookie 传递。 服务器可以使用URL 重写技术,把 JSESSIONID 直接拼接到访问 URL 后面。 客户端每次请求带上这个带 JSESSIONID 的 URL,服务器照样能找到对应的 Session。

7、JSP & EL & JSTL

1、JSP 本质是什么

JSP 的本质就是一个 Servlet。 JSP 在运行时会被服务器翻译成Java 类,这个类就是 Servlet 然后编译成 class 文件执行 本质还是用 Java 代码输出 HTML

2、JSP 九大内置对象

  1. request请求对象
  2. response响应对象
  3. session会话对象
  4. application应用全局对象
  5. pageContext页面上下文对象
  6. page当前页面对象(this)
  7. out输出对象
  8. config配置对象
  9. exception异常对象

3、四个作用域从小到大

pageContext (本页面)→ request (一次请求) → session (一次会话) → application (应用程序)

4、EL 表达式

EL(Expression Language)表达式语言,用于在 JSP 页面中简洁获取数据,替代繁琐的 Java 代码。 语法:${表达式} 常用场景:

  1. 取作用域值 ${ name } ${ user.username }
  2. 取作用域(指定域) ${ pageScope.key } ${ requestScope.key } ${ sessionScope.key } ${ applicationScope.key }
  3. 判空 ${ empty user }
  4. 运算 ${ 10 + 20 } ${ a == b }

5、JSTL

JSTL = JSP Standard Tag Library,JSP 标准标签库

作用:

替代 JSP 里的<% %> Java 代码 实现分支、循环、格式化、数据库操作等逻辑 让 JSP 更整洁、易维护

常用标签库:

  1. 核心标签库 c:
c:if判断
c:forEach循环
c:set存值
c:out输出
  1. 格式化标签库 fmt: 日期、数字格式化
  2. SQL 标签库 sql: 数据库操作

使用步骤:

  1. 引入 jar 包
  2. JSP 顶部用taglib指令导入
  3. 直接使用标签

8、Filter & Listener

1、Filter 是什么

Filter 就是过滤器,运行在 Servlet 之前,对请求和响应进行统一拦截处理。

作用:

统一编码处理 权限校验 日志记录 敏感词过滤 资源访问控制

生命周期方法:

  1. init():初始化
  2. doFilter():过滤逻辑(核心)
  3. destroy():销毁

执行流程:

请求→ Filter → Servlet →响应

2、Filter 生命周期

Filter 生命周期和 Servlet 非常像,一共3 个阶段,对应3 个方法:

  1. init(FilterConfig config) 服务器启动 / 第一次请求时初始化 只执行1 次
  2. doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 每次请求都会经过 核心过滤逻辑写在这里
必须调用:chain.doFilter(request, response)才会放行
  1. destroy() 服务器正常关闭时执行 做资源释放 只执行1 次

3、FilterChain 作用

FilterChain 就是过滤器链,用来依次调用多个 Filter,并最终访问目标资源(Servlet/JSP)。

核心功能:

  1. 管理多个 Filter 的执行顺序 按照web.xml或注解配置的顺序,依次执行每个 Filter。
  2. 放行到下一个 Filter / 目标资源
调用chain.doFilter(request, response)才会继续执行,否则请求被拦截。

执行流程:

请求→ Filter1 → Filter2 → … → Servlet →响应原路返回

4、Listener 是什么

Listener 是监听器,用于监听 Web 应用中对象(如 request、session、ServletContext)的创建、

销毁、属性变化等事件,并自动触发对应的方法。

作用:

监听域对象的创建 / 销毁 监听域对象属性的增删改 做初始化、统计、在线用户、日志等

三大域对象监听:

  1. ServletContextListener:监听应用启动 / 关闭
  2. HttpSessionListener:监听会话创建 / 销毁
  3. ServletRequestListener:监听请求创建 / 销毁

5、Listener和Filter区别

Filter 是拦请求做处理,Listener 是听事件做响应。

对比维

度

Filter(过滤器)

Listener(监听器)

作用

拦截请求 / 响应,做预处理、过 滤 监听域对象的创建、销毁、属性变化

触发时

机

请求到达 / 响应返回时 事件发生时(创建、销毁、设值)

核心接

口

Filter ServletContextListener / HttpSessionListener 等

核心方

法

doFilter contextInitialized、sessionCreated 等

是否拦

截

可以拦截、放行、中断 不能拦截,只能监听通知

典型场

景

编码、权限、日志、限流 在线人数、项目初始化、统计

9、编码、乱码问题

1、JavaWeb 乱码原因

根本原因:编码和解码用的字符集不一致。

常见乱码场景:

  1. 请求参数乱码(GET/POST)
  2. 响应输出乱码(页面中文乱码)
  3. 数据库存取乱码
  4. JSP 页面本身乱码

2、解决乱码方案

  1. 页面编码
  2. 请求编码
  3. 响应编码
<%@pagecontentType="text/html; charset=UTF-8" %>
request.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=UTF-8");
response.setCharacterEncoding("UTF-8");
  1. 服务器编码
  2. 数据库编码 建库 / 建表:UTF8 / UTF8MB4
连接串:useUnicode=true&characterEncoding=UTF-8

二、Spring

1、什么是 Spring IOC 容器

IoC就是控制反转,将对象的创建权交由Spring管理,IoC是一个容器,里面管理的是各式各样的组件。 开发人员通过IoC容器进行获取对象,以DI的方式进行注入。

2、如何实现一个Spring容器

1、配置文件配置包扫描路径 2、递归包扫描获取.class文件 3、反射、确定需要交给IOC管理的类 4、对需要注入的类进行依赖注入 配置文件中指定需要扫描的包路径 定义一些注解,分别表示访问控制层、业务服务层、数据持久层、依赖注入注解、获取配置文件注 解 从配置文件中获取需要扫描的包路径,获取到当前路径下的文件信息及文件夹信息,我们将当前路 径下所有以.class结尾的文件添加到一个Set集合中进行存储 遍历这个set集合,获取在类上有指定注解的类,并将其交给IOC容器,定义一个安全的Map用来存储这 些对象 遍历这个IOC容器,获取到每一个类的实例,判断里面是有有依赖其他的类的实例,然后进行递归注 入。

3、什么是依赖注入?可以通过多少种方式完成依赖注入

URIEncoding="UTF-8"

DI是依赖注入,无论是程序员通过容器获取对象,亦或是Spring容器内部组装对象之间的关系,由程序 员获取。解耦。不在代码中强制写死对象之间的依赖关系。 通常,依赖注入可以通过三种方式完成,即: 构造函数注入 setter 注入 接口注入 在 Spring Framework 中,仅使用构造函数和 setter 注入。

4、BeanFactory 和 ApplicationContext的区别

ApplicationContext是BeanFactory的子接口 ApplicationContext提供了更完整的功能: ①继承MessageSource,因此支持国际化。 ②统一的资源文件访问方式。 ③提供在监听器中注册bean的事件。 ④同时加载多个配置文件。 ⑤载入多个(有继承关系)上下文,使得每一个上下文都专注于一个特定的层次,比如应用的web层。 BeanFactroy采用的是延迟加载形式来注入Bean的,即只有在使用到某个Bean时(调用 getBean()),才对该Bean进行加载实例化。这样,我们就不能发现一些存在的Spring的配置问 题。如果Bean的某一个属性没有注入,BeanFacotry加载后,直至第一次使用调用getBean方法才会抛 出异常。 ApplicationContext,它是在容器启动时,一次性创建了所有的Bean。这样,在容器启动时,我 们就可以发现Spring中存在的配置错误,这样有利于检查所依赖属性是否注入。 ApplicationContext启动后预载入所有的单实例Bean,通过预载入单实例bean ,确保当你需要的时候, 你就不用等待,因为它们已经创建好了。 相对于基本的BeanFactory,ApplicationContext 唯一的不足是占用内存空间。当应用程序配置 Bean较多时,程序启动较慢。 BeanFactory通常以编程的方式被创建,ApplicationContext还能以声明的方式创建,如使用 ContextLoader。 BeanFactory和ApplicationContext都支持BeanPostProcessor、BeanFactoryPostProcessor的 使用,但两者之间的区别是:BeanFactory需要手动注册,而ApplicationContext则是自动注册

5、构造函数注入和 setter 注入

构造函数注入和setter注入是依赖注入(Dependency Injection)中两种常见的方式,用于将依赖对象 注入到目标对象中。它们在注入方式和使用方式上有所不同。

  1. 构造函数注入(Constructor Injection): 构造函数注入是通过目标对象的构造函数来接收依赖对象的注入。 依赖对象在创建目标对象时通过构造函数的参数传递进来。 一旦目标对象被创建,其依赖对象就不能再改变。 构造函数注入可以保证目标对象在创建时就具备了必要的依赖,使得目标对象的状态是完整和 一致的。 示例(Java):
  2. Setter注入(Setter Injection): Setter注入是通过目标对象的setter方法来接收依赖对象的注入。 依赖对象通过setter方法设置到目标对象中。 可以在任何时候通过调用setter方法来改变目标对象的依赖对象。 Setter注入可以提供更灵活的依赖注入方式,允许在运行时动态地改变依赖对象。 示例(Java): 总结: 构造函数注入适用于那些在目标对象创建时就必须具备依赖对象的情况,可以保证目标对象的依赖 是不可变的。 Setter注入适用于那些依赖对象可以在运行时动态改变的情况,提供了更大的灵活性。 在选择注入方式时,应根据具体的需求和设计考虑使用哪种方式,或者在需要时结合两种方式使 用。

6、Spring 提供了哪些配置方式

基于 xml 配置 bean 所需的依赖项和服务在 XML 格式的配置文件中指定。这些配置文件通常包含许多 bean 定义和特 定于应用程序的配置选项。它们通常以 bean 标签开头。例如: 基于注解配置

publicclassUserService {
privateUserRepositoryuserRepository;
publicUserService(UserRepositoryuserRepository) {
this.userRepository=userRepository;
}
// 使用userRepository进行用户操作
}
publicclassUserService {
privateUserRepositoryuserRepository;
publicvoidsetUserRepository(UserRepositoryuserRepository) {
this.userRepository=userRepository;
}
// 使用userRepository进行用户操作
}
<beanid="studentbean"class="org.yuma.firstSpring.StudentBean">
<propertyname="name"value="yuma"></property>
</bean>

您可以通过在相关的类,方法或字段声明上使用注解,将 bean 配置为组件类本身,而不是使用 XML 来 描述 bean 装配。默认情况下,Spring 容器中未打开注解装配。因此,您需要在使用它之前在 Spring 配 置文件中启用它。例如: 基于 Java Config配置—目前使用主流。 Spring 的 Java 配置是通过使用 @Bean 和 @Configuration 来实现。

  1. @Bean 注解扮演与元素相同的角色。
  2. @Configuration 类允许通过简单地调用同一个类中的其他 @Bean 方法来定义 bean 间依赖关系。
  3. @Component注解 例如:

7、Spring 中的 bean 的作用域有哪些

singleton:默认,每个容器中只有一个bean的实例,单例的模式由BeanFactory自身来维护。该对象 的生命周期是与Spring IOC容器一致的(但在第一次被注入时才会创建) prototype : 每次请求都会创建一个新的 bean 实例 request:bean被定义为在每个HTTP请求中创建一个单例对象,也就是说在单个请求中都会复用这一 个单例对象。 session:与request范围类似,确保每个session中有一个bean的实例,在session过期后,bean会随 之失效。 application:bean被定义为在ServletContext的生命周期中复用一个单例对象。 websocket:bean被定义为在websocket的生命周期中复用一个单例对象。 global-session:全局作用域,global-session和Portlet应用相关。当你的应用部署在Portlet容器中工 作时,它包含很多portlet。如果你想要声明让所有的portlet共用全局的存储变量的话,那么这全局变量 需要存储在global-session中。全局作用域与Servlet中的session作用域效果相同。

<beans>
<context:annotation-config/>
<!-- bean definitions go here -->
</beans>
@Configuration
publicclassAppConfig{
@Bean
publicStudentStudent () {
returnnewStudent ();
}
}
@Component
publicclassPerson{
}

refesh:Springcloud组件扩展了Bean对象的作用域在一次配置文件修改之后有效。

8、深入谈谈对Ioc的理解

容器概念、控制反转、依赖注入 Ioc容器: 实际上就是个map(key,value),里面存的是各种对象(在xml里配置的bean节点、@repository、 @service、@controller、@component),在项目启动的时候会读取配置文件里面的bean节点,根据 全限定类名使用反射创建对象放到map里、扫描到打上上述注解的类还是通过反射创建对象放到map 里。这个时候map里就有各种对象了,接下来我们在代码里需要用到里面的对象时,再通过DI注入 (@autowired、@resource等注解,xml里bean节点内的ref属性,项目启动的时候会读取xml节点ref 属性根据id注入,也会扫描这些注解,根据类型或id注入;id就是对象名)。 控制反转: 没有引入IOC容器之前,对象A依赖于对象B,那么对象A在初始化或者运行到某一点的时候,自己必须主 动去创建对象B或者使用已经创建的对象B。无论是创建还是使用对象B,控制权都在自己手上。引入 IOC容器之后,对象A与对象B之间失去了直接联系,当对象A运行到需要对象B的时候,IOC容器会主动 创建一个对象B注入到对象A需要的地方。 通过前后的对比,不难看出来:对象A获得依赖对象B的过程,由主动行为变为了被动行为,控制权颠倒过 来了,这就是“控制反转”这个名称的由来。全部对象的控制权全部上缴给“第三方”IOC容器,所以,IOC 容器成了整个系统的关键核心,它起到了一种类似“粘合剂”的作用,把系统中的所有对象粘合在一起发 挥作用,如果没有这个“粘合剂”,对象与对象之间会彼此失去联系,这就是有人把IOC容器比喻成“粘合 剂”的由来。

依赖注入:

“获得依赖对象的过程被反转了”。控制被反转之后,获得依赖对象的过程由自身管理变为了由IOC容器主 动注入。依赖注入是实现IOC的方法,就是由IOC容器在运行期间,动态地将某种依赖关系注入到对象之 中。

9、将一个类声明为Spring的 bean 的注解有哪些

我们一般使用 @Autowired 注解自动装配 bean,要想把类标识成可用于 @Autowired 注解自动装配的 bean 的类,采用以下注解可实现: @Component :通用的注解,可标注任意类为 Spring 组件。如果一个Bean不知道属于哪个层, 可以使用@Component 注解标注。 @Repository : 对应持久层即 Dao 层,主要用于数据库相关操作。 @Service : 对应服务层,主要涉及一些复杂的逻辑,需要用到 Dao层。 @Controller : 对应 Spring MVC 控制层,主要用户接受用户请求并调用 Service 层返回数据给前端 页面。 @Configuration+@Bean的方式

10、Spring 中的 bean 生命周期

1.扫描生成BeanDefinition,并注册到BeanDefinitionMap中 2.合并得到BeanDefiniton. 3.加载类resolveBeanClass 4.实例化前 5.实例化 6.实例化后 7.属性填充 8.执行各种Aware 9.初始化前 10.初始化 11.初始化后 12.放入单例池 13.使用Bean对象 14.销毁Spring容器关闭时调用DisposableBean中destory()方法。 其中在实例化前后以及初始化前后程序员都可以通过BeanPostProcessor或者BeanPostProcessor接口 的子接口InstantiationAwareBeanPostProcessor进行扩展。 因为Bean的生命周期涉及到的细节太多了…具体源码分析流程,可参考面试题精讲。

11、什么是bean的自动装配,有哪些方式

开启自动装配,只需要在xml配置文件中定义“autowire”属性。 autowire属性有六种装配的方式: 1、no – 缺省情况下,自动配置是通过“ref”属性手动设定。 手动装配:以value或ref的方式明确指定属性值都是手动装配。 需要通过‘ref’属性来连接bean。 2、byName-根据bean的属性名称进行自动装配。 3、byType-根据bean的类型进行自动装配

<beanid="cutomer"class="com.yuma.Customer"autowire=""/>
Cutomer的属性名称是person,Spring会将bean id为person的bean通过setter方法进行自动装

配。

<beanid="cutomer"class="com.yuma.Cutomer"autowire="byName"/>
<beanid="person"class="com.yuma.Person"/>
Cutomer的属性person的类型为Person,Spirng会将Person类型通过setter方法进行自动装配。
<beanid="cutomer"class="com.yuma.Cutomer"autowire="byType"/>
<beanid="person"class="com.yuma.Person"/>

4、constructor-类似byType,不过是应用于构造器的参数。如果一个bean与构造器参数的类型形相 同,则进行自动装配,否则导致失效。 5、autodetect-如果有默认的构造器,则通过constructor方式进行自动装配,否则使用byType方式进 行自动装配。但是spring3.0+已将该值废弃。 6、 default:由上级标签的default-autowire属性确定。 @Autowired自动装配bean,可以在字段、setter方法、构造函数上使用。相比Spring自带的自动注入 方式,更加灵活。

12、Spring中出现同名bean怎么办

如果是在不同的@Component注解中定义的同一个BeanName,那么Spring会直接报错。 如果是ComponentScan和@Bean出现同名Bean。那么@Bean的会生效,@ComponentScan扫描 进来不会生效。原因就是扫描进来的Bean定义是最先被注册的,而Spring默认又是支持 BeanDefinition重写。也即allowBeanDefinitionOverriding为true,因此Spring底层就会以后解析 的@Bean生成一个新BeanDefinition,且名字仍然是BeanName.然后注册到BeanDefinitonMap 中,因此对于Map来说,同一个key的value值以最后一次为准。

13、Spring 怎么解决循环依赖问题

什么是循环依赖:

Cutomer构造函数的参数person的类型为Person,Spirng会将Person类型通过构造方法进行自动装

配。

<beanid="cutomer"class="com.yuma.Cutomer"autowire="construtor"/>
<beanid="person"class="com.yuma.Person"/>

如果有默认的构造器,则通过constructor方式进行自动装配,否则使用byType方式进行自动装配 这里不会对Bean的生命周期进行详细的描述,只描述一下大概的过程。 Bean的生命周期指的就是:在Spring中,Bean是如何生成的? 被Spring管理的对象叫做Bean。Bean的生成步骤如下:

  1. Spring扫描class得到BeanDefinition
  2. 根据得到的BeanDefinition去生成bean
  3. 首先根据class推断构造方法
  4. 根据推断出来的构造方法,反射,得到一个对象(暂时叫做原始对象)
  5. 填充原始对象中的属性(依赖注入)
  6. 如果原始对象中的某个方法被AOP了,那么则需要根据原始对象生成一个代理对象
  7. 把最终生成的代理对象放入单例池(源码中叫做singletonObjects)中,下次getBean时就直接从 单例池拿即可 可以看到,对于Spring中的Bean的生成过程,步骤还是很多的,并且不仅仅只有上面的7步,还有很多 很多,比如Aware回调、初始化等等,不详细讨论。 可以发现,在Spring中,构造一个Bean,包括了new这个步骤(第4步构造方法反射)。 得到一个原始对象后,Spring需要给对象中的属性进行依赖注入,那么这个注入过程是怎样的? 比如上文说的A类,A类中存在一个B类的b属性,所以,当A类生成了一个原始对象之后,就会去给b属 性去赋值,此时就会根据b属性的类型和属性名去BeanFactory中去获取B类所对应的单例bean。如果此 时BeanFactory中存在B对应的Bean,那么直接拿来赋值给b属性;如果此时BeanFactory中不存在B对 应的Bean,则需要生成一个B对应的Bean,然后赋值给b属性。 问题就出现在第二种情况,如果此时B类在BeanFactory中还没有生成对应的Bean,那么就需要去生 成,就会经过B的Bean的生命周期。 那么在创建B类的Bean的过程中,如果B类中存在一个A类的a属性,那么在创建B的Bean的过程中就需 要A类对应的Bean,但是,触发B类Bean的创建的条件是A类Bean在创建过程中的依赖注入,所以这里 就出现了循环依赖: ABean创建—>依赖了B属性—>触发BBean创建--->B依赖了A属性--->需要ABean(但ABean还在创建过程 中) 从而导致ABean创建不出来,BBean也创建不出来。

如何解决循环依赖:

Spring底层最终解决靠的是四个Map(其中三个缓存Map,还有一个是判断有没有提前进行过Aop的Map 以及一个Set(判断当前对象是否处于正在创建中)

  1. singletonObjects:缓存某个beanName对应的经过了完整生命周期的bean也即我们平时说的单 例池。
  2. earlySingletonObjects:缓存提前通过原始对象进行了AOP之后得到的代理对象,原始对象还没 有进行属性注入和后续的BeanPostProcessor等生命周期
  3. singletonFactories:缓存的是一个ObjectFactory,也就是一个Lambda表达式。在创建一个 Bean时,在每个Bean的生成过程中,都会提前暴露一个Lambda表达式,并保存到三级缓存中, 这个Lambda表达式可能用到,也可能用不到,如果没有出现循环依赖依赖本bean,那么这个 Lambda表达式无用,本bean按照自己的生命周期执行,执行完后直接把本bean放入 singletonObjects中即可,如果出现了循环依赖依赖了本bean,则从三级缓存中获取Lambda表达 式,并执行Lambda表达式得到一个AOP之后的代理对象(如果有AOP的话,如果无需AOP,则直接 得到一个原始对象),并把得到的对象放入二级缓存
  4. 其实还要一个缓存,就是earlyProxyReferences,它用来记录某个原始对象是否进行过AOP了。
  5. creatingMap 这个作用是去判断是不是出现了循环依赖也即是不是某个类是不是在创建中。

14、Spring 中的单例 bean 的线程安全问题

Spring中的Bean默认是单例模式的,框架并没有对bean进行多线程的封装处理。 如果Bean是有状态的那就需要开发人员自己来进行线程安全的保证,最简单的办法就是改变bean的作 用域把 “singleton”改为”protopyte” 这样每次请求Bean就相当于是 new Bean() 这样就可以保证线程的 安全了。有状态就是有数据存储功能。无状态就是不会保存数据 controller、service和dao层本身并不 是线程安全的,只是如果只是调用里面的方法,而且多线程调用一个实例的方法,会在内存中复制变 量,这是自己的线程的工作内存,是安全的。 Dao会操作数据库Connection,Connection是带有状态的,比如说数据库事务,Spring的事务管理器使 用Threadlocal为不同线程维护了一套独立的connection副本,保证线程之间不会互相影响(Spring是如 何保证事务获取同一个Connection的) 不要在bean中声明任何有状态的实例变量或类变量,如果必须如此,那么就使用ThreadLocal把变量变 为线程私有的,如果bean的实例变量或类变量需要在多个线程之间共享,那么就只能使用 synchronized、lock、CAS等这些实现线程同步的方法了.

15、什么是 AOP

AOP(Aspect-Oriented Programming), 即面向切面编程, 它与 OOP( Object-Oriented Programming, 面向对象编程) 相辅相成, 提供了与 OOP 不同的抽象软件结构的视角. 在 OOP 中, 我们以类(class)作为我 们的基本单元, 而 AOP 中的基本单元是Aspect(切面)

16、谈谈对Aop的理解

系统是由许多不同的组件所组成的,每一个组件各负责一块特定功能。除了实现自身核心功能之外,这 些组件还经常承担着额外的职责。例如日志、事务管理和安全这样的核心服务经常融入到自身具有核心 业务逻辑的组件中去。这些系统服务经常被称为横切关注点,因为它们会跨越系统的多个组件。当我们 需要为分散的对象引入公共行为的时候,OOP则显得无能为力。也就是说,OOP允许你定义从上到下的 关系,但并不适合定义从左到右的关系。例如日志功能。 日志代码往往水平地散布在所有对象层次中,而与它所散布到的对象的核心功能毫无关系。 在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。 AOP:将程序中的交叉业务逻辑(比如安全,日志,事务等),封装成一个切面,然后注入到目标对象 (具体业务逻辑)中去。AOP可以对某个对象或某些对象的功能进行增强,比如对象中的方法进行增 强,可以在执行某个方法之前额外的做一些事情,在某个方法执行之后额外的做一些事情。

17、AOP 有哪些实现方式

实现 AOP 的技术,主要分为两大类: 静态代理 - 指使用 AOP 框架提供的命令进行编译,从而在编译阶段就可生成 AOP 代理类,因此也 称为编译时增强; 编译时编织(特殊编译器实现) 类加载时编织(特殊的类加载器实现)。 动态代理 - 在运行时在内存中“临时”生成 AOP 动态代理类,因此也被称为运行时增强。 JDK 动态代理:通过反射来接收被代理的类,并且要求被代理的类必须实现一个接口。JDK 动 态代理的核心是 InvocationHandler 接口和 Proxy 类。 CGLIB动态代理:如果目标类没有实现接口,那么 Spring AOP 会选择使用 CGLIB 来动态代 理目标类。CGLIB ,是一个代码生成的类库,可以在运行时动态的生成某个类的子类,注 意, CGLIB 是通过继承的方式做的动态代理,因此如果某个类被标记为 final ,那么它是无法 使用 CGLIB 做动态代理的。

18、Spring 框架中用到了哪些设计模式

1、工厂设计模式 : 简单工厂:由一个工厂类根据传入的参数,动态决定应该创建哪一个产品类 Spring中的BeanFactory就是简单工厂模式的体现,根据传入一个唯一的标识来获得Bean对象,但是否 是 在传入参数后创建还是传入参数前创建这个要根据具体情况来定。

工厂方法:

实现了FactoryBean接口的bean是一类叫做factory的bean。其特点是,spring会在使用getBean()调用 获得该bean时,会自动调用该bean的getObject()方法,所以返回的不是factory这个bean,而是这个 bean.getOjbect()方法的返回值。

2、单例模式

保证一个类仅有一个实例,并提供一个访问它的全局访问点。 spring对单例的实现: spring中的单例模式完成了后半句话,即提供了全局的访问点BeanFactory。但 没有从构造器级别去控制单例,这是因为Spring管理的是任意的java对象。

3、适配器模式:

Spring定义了一个适配接口,使得每一种Controller有一种对应的适配器实现类,让适配器代替 controller执行相应的方法。这样在扩展Controller时,只需要增加一个适配器类就完成了SpringMVC的 扩展了。

4、装饰器模式:

动态地给一个对象添加一些额外的职责。就增加功能来说,Decorator模式相比生成子类 更为灵活。 Spring中用到的装饰器模式在类名上有两种表现:一种是类名中含有Wrapper,另一种是类名中含有 Decorator。

5、代理模式:

切面在应用运行的时刻被织入。一般情况下,在织入切面时,AOP容器会为目标对象创建动态的创建一 个代理对象。SpringAOP就是以这种方式织入切面的。 织入:把切面应用到目标对象并创建新的代理对象的过程。 6、观察者模式: spring的事件驱动模型使用的是观察者模式,Spring中Observer模式常用的地方是listener的实现。

7、策略模式:

Spring框架的资源访问Resource接口。该接口提供了更强的资源访问能力,Spring 框架本身大量使用了 Resource 接口来访问底层资源。 8、模板方法模式 : 父类定义了骨架(调用哪些方法及顺序),某些特定方法由子类实现 Spring 中 jdbcTemplate、hibernateTemplate 等以 Template 结尾的对数据库操作的类,它们就使用 到了模板模式。

19、Spring 事务实现方式有哪些以及原理

编程式事务管理:这意味着你可以通过编程的方式管理事务,这种方式带来了很大的灵活性,但很 难维护。 声明式事务管理:这种方式意味着你可以将事务管理和业务代码分离。你只需要通过注解或者XML 配置管理事务。@Transactional注解就是声明式事务。 首先,事务这个概念是数据库层面的,Spring只是基于数据库中的事务进行了扩展,以及提供了一些能 让程序员更加方便操作事务的方式。 比如我们可以通过在某个方法上增加@Transactional注解,就可以开启事务,这个方法中所有的sql都会 在一个事务中执行,统一成功或失败。 在一个方法上加了@Transactional注解后,Spring会基于这个类生成一个代理对象,会将这个代理对象 作为bean,当在使用这个代理对象的方法时,如果这个方法上存在@Transactional注解,那么代理逻辑 会先把事务的自动提交设置为false,然后再去执行原本的业务逻辑方法,如果执行业务逻辑方法没有出 现异常,那么代理逻辑中就会将事务进行提交,如果执行业务逻辑方法出现了异常,那么则会将事务进 行回滚。当然,针对哪些异常回滚事务是可以配置的,可以利用@Transactional注解中的rollbackFor属 性进行配置,默认情况下会对RuntimeException和Error进行回滚。

20、Spring事务的隔离级别

spring事务隔离级别就是数据库的隔离级别:外加一个默认级别 read uncommitted(未提交读) read committed(提交读、不可重复读) repeatable read(可重复读) serializable(可串行化)

注意:

数据库的配置隔离级别是Read Commited,而Spring配置的隔离级别是Repeatable Read,请问这时隔离 级别是以哪一个为准? 以Spring配置的为准,如果spring设置的隔离级别数据库不支持,效果取决于数据库。

21、Spring事务定义的传播规则

多个事务方法相互调用时,事务如何在这些方法间传播。 方法A是一个事务的方法,方法A执行过程中调用了方法B,那么方法B有无事务以及方法B对事务的要求 不同都会对方法A的事务具体执行造成影响,同时方法A的事务对方法B的事务执行也有影响,这种影响 具体是什么就由两个方法所定义的事务传播类型所决定。 REQUIRED(Spring默认的事务传播类型):如果当前没有事务,则自己新建一个事务,如果当前存在事 务,则加入这个事务。SUPPORTS:当前存在事务,则加入当前事务,如果当前没有事务,就以非事务 方法执行。 MANDATORY:当前存在事务,则加入当前事务,如果当前事务不存在,则抛出异常。 REQUIRES_NEW:创建一个新事务,如果存在当前事务,则挂起该事务。 NOT_SUPPORTED:以非事务方式执行,如果当前存在事务,则挂起当前事务。 NEVER:不使用事务,如果当前事务存在,则抛出异常。 NESTED:如果当前事务存在,则在嵌套事务中执行,否则REQUIRED的操作一样(开启一个事务)。 NESTED和REQUIRES_NEW的区别 REQUIRES_NEW是新建一个事务并且新开启的这个事务与原有事务无关,而NESTED则是当前存在 事务时(我们把当前事务称之为父事务)会开启一个嵌套事务(称之为一个子事务)。在NESTED 情况下父事务回滚时,子事务也会回滚,而在REQUIRES_NEW情况下,原有事务回滚,不会影响 新开启的事务。 NESTED和REQUIRED的区别 REQUIRED情况下,调用方存在事务时,则被调用方和调用方使用同一事务,那么被调用方出现异 常时,由于共用一个事务,所以无论调用方是否catch其异常,事务都会回滚而在NESTED情况 下,被调用方发生异常时,调用方可以catch其异常,这样只有子事务回滚,父事务不受影响

22、Spring事务什么时候会失效

Spring事务的原理是AOP,进行了切面增强,那么失效的根本原因是这个AOP不起作用了!常见情况有 如下几种 1、发生自调用,类里面使用this调用本类的方法(this通常省略),此时这个this对象不是代理类,而 是该类的实例对象本身! 解决方法很简单,让那个this变成该类的的代理类实例对象即可! 2、方法不是public的 3、数据库不支持事务 4、没有被Spring管理 5、异常被吃掉,事务不会回滚(或者抛出的异常没有被定义,默认为RuntimeException)

23、SpringMVC 工作原理了解吗

流程说明(重要):

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

24、简单介绍 Spring MVC 的核心组件

Handler:也就是处理器。它直接应对着MVC中的C也就是Controller层,它的具体表现形式有很多,可 以是类,也可以是方法。在Controller层中@RequestMapping标注的所有方法都可以看成是一个 Handler,只要可以实际处理请求就可以是Handler。 1、HandlerMapping initHandlerMappings(context),处理器映射器,根据用户请求的资源uri来查找Handler的。在 SpringMVC中会有很多请求,每个请求都需要一个Handler处理,具体接收到一个请求之后使用哪个 Handler进行,这就是HandlerMapping需要做的事。 2、HandlerAdapter initHandlerAdapters(context),适配器。因为SpringMVC中的Handler可以是任意的形式,只要能处理 请求就ok,但是Servlet需要的处理方法的结构却是固定的,都是以request和response为参数的方法。 如何让固定的Servlet处理方法调用灵活的Handler来进行处理呢?这就是HandlerAdapter要做的事情。 Handler是用来干活的工具;HandlerMapping用于根据需要干的活找到相应的工具;HandlerAdapter 是使用工具干活的人 3、HandlerExceptionResolver initHandlerExceptionResolvers(context),其它组件都是用来干活的。在干活的过程中难免会出现问 题,出问题后怎么办呢?这就需要有一个专门的角色对异常情况进行处理,在SpringMVC中就是 HandlerExceptionResolver。具体来说,此组件的作用是根据异常设置ModelAndView,之后再交给 render方法进行渲染。 4、ViewResolver initViewResolvers(context),ViewResolver用来将String类型的视图名和Locale解析为View类型的视 图。View是用来渲染页面的,也就是将程序返回的参数填入模板里,生成html(也可能是其它类型)文 件。这里就有两个关键问题:使用哪个模板?用什么技术(规则)填入参数?这其实是ViewResolver主 要要做的工作,ViewResolver需要找到渲染所用的模板和所用的技术(也就是视图的类型)进行渲染, 具体的渲染过程则交由不同的视图自己完成。 5、RequestToViewNameTranslator initRequestToViewNameTranslator(context),ViewResolver是根据ViewName查找View,但有的 Handler处理完后并没有设置View也没有设置ViewName,这时就需要从request获取ViewName了,如 何从request中获取ViewName就是RequestToViewNameTranslator要做的事情了。 RequestToViewNameTranslator在Spring MVC容器里只可以配置一个,所以所有request到ViewName 的转换规则都要在一个Translator里面全部实现。 6、LocaleResolver initLocaleResolver(context),解析视图需要两个参数:一是视图名,另一个是Locale。视图名是处理 器返回的,Locale是从哪里来的?这就是LocaleResolver要做的事情。LocaleResolver用于从request解 析出Locale,Locale就是zh-cn之类,表示一个区域,有了这个就可以对不同区域的用户显示不同的结 果。SpringMVC主要有两个地方用到了Locale:一是ViewResolver视图解析的时候;二是用到国际化资 源或者主题的时候。 7、ThemeResolver initThemeResolver(context),用于解析主题。SpringMVC中一个主题对应一个properties文件,里面 存放着跟当前主题相关的所有资源、如图片、css样式等。SpringMVC的主题也支持国际化,同一个主题 不同区域也可以显示不同的风格。SpringMVC中跟主题相关的类有 ThemeResolver、ThemeSource和 Theme。主题是通过一系列资源来具体体现的,要得到一个主题的资源,首先要得到资源的名称,这是 ThemeResolver的工作。然后通过主题名称找到对应的主题(可以理解为一个配置)文件,这是 ThemeSource的工作。最后从主题中获取资源就可以了。 8、MultipartResolver initMultipartResolver(context),用于处理上传请求。处理方法是将普通的request包装成 MultipartHttpServletRequest,后者可以直接调用getFile方法获取File,如果上传多个文件,还可以调 用getFileMap得到FileName->File结构的Map。此组件中一共有三个方法,作用分别是判断是不是上传 请求,将request包装成MultipartHttpServletRequest、处理完后清理上传过程中产生的临时资源。 9、FlashMapManager initFlashMapManager(context),用来管理FlashMap的,FlashMap主要用在redirect中传递参数。

25、@Controller 注解有什么用

@Controller 注解标记一个类为 Spring Web MVC 控制器 Controller。Spring MVC 会将扫描到该注解的 类,然后扫描这个类下面带有 @RequestMapping 注解的方法,根据注解信息,为这个方法生成一个对 应的处理器对象,在上面的 HandlerMapping 和 HandlerAdapter组件中讲到过。当然,除了添加 @Controller 注解这种方式以外,你还可以实现 Spring MVC 提供的 Controller 或者 HttpRequestHandler 接口,对应的实现类也会被作为一个处理器对象

26、@RestController 和 @Controller 有什么区别

@RestController 注解,在 @Controller 基础上,增加了 @ResponseBody 注解,更加适合目前前后端 分离的架构下,提供 Restful API ,返回例如 JSON 数据格式。当然,返回什么样的数据格式,根据客户 端的 ACCEPT 请求头来决定。

27、@RequestMapping 和 @GetMapping 注解的不同

之处在哪里

  1. @RequestMapping:可注解在类和方法上;@GetMapping 仅可注册在方法上
  2. @RequestMapping:可进行 GET、POST、PUT、DELETE 等请求方法;@GetMapping 是 @RequestMapping 的 GET 请求方法的特例,目的是为了提高清晰度。

28、@RequestParam 和 @PathVariable 两个注解的区

别

两个注解都用于方法参数,获取参数值的方式不同,@RequestParam 注解的参数从请求携带的参数中 获取,而 @PathVariable 注解从请求的 URI 中获取。

29、返回 JSON 格式使用什么注解

可以使用@ResponseBody注解,或者使用包含 @ResponseBody 注解的@RestController注解。 当然,还是需要配合相应的支持 JSON 格式化的 HttpMessageConverter 实现类。例如,Spring MVC 默认使用 MappingJackson2HttpMessageConverter。

30、什么是springmvc拦截器以及如何使用它

Spring MVC拦截器(Interceptor)是一种用于拦截和处理请求的组件,它可以在请求的前后进行处理, 并对请求进行修改或添加额外的功能。拦截器可以用于实现一些通用的功能,例如身份验证、日志记 录、性能监控等。 以下是使用Spring MVC拦截器的步骤:

  1. 创建拦截器类: 创建一个实现HandlerInterceptor接口的拦截器类。 拦截器类可以包含在Spring MVC应用程序的任何位置,例如包下的一个单独类或者一个独立 的模块。
  2. 实现拦截器方法: 在拦截器类中实现preHandle、postHandle和afterCompletion等方法。 preHandle方法在请求处理之前执行,可以进行一些前置处理,例如身份验证、权限检查等。 postHandle方法在请求处理之后、视图渲染之前执行,可以对模型数据进行处理或添加额外 的功能。 afterCompletion方法在整个请求完成之后执行,可以进行一些资源清理或日志记录等操作。
  3. 配置拦截器: 在Spring MVC配置文件(如XML配置文件或Java配置类)中配置拦截器。 通过注册拦截器类的方式将其添加到拦截器链中。 可以指定拦截器的拦截路径(URL模式)或排除路径,以确定哪些请求会被拦截。
  4. 测试拦截器: 启动Spring MVC应用程序,并发送请求进行测试。 拦截器将根据配置的规则拦截相应的请求,并执行拦截器方法。 通过使用Spring MVC拦截器,您可以在请求的前后进行处理,实现一些通用的功能和逻辑。拦截器提供 了一种灵活的方式来对请求进行拦截和处理,使您能够在应用程序中添加额外的功能和行为。

31、为什么要用SpringBoot

在使用Spring框架进行开发的过程中,需要配置很多Spring框架包的依赖,如spring-core、spring- bean、spring-context等,而这些配置通常都是重复添加的,而且需要做很多框架使用及环境参数的重 复配置,如开启注解、配置日志等。Spring Boot致力于弱化这些不必要的操作,提供默认配置,当然这 些默认配置是可以按需修改的,快速搭建、开发和运行Spring应用。 以下是使用SpringBoot的一些好处: 自动配置,使用基于类路径和应用程序上下文的智能默认值,当然也可以根据需要重写它们以满足 开发人员的需求。 创建Spring Boot Starter 项目时,可以选择选择需要的功能,Spring Boot将为你管理依赖关系。 SpringBoot项目可以打包成jar文件。可以使用Java-jar命令从命令行将应用程序作为独立的Java应 用程序运行。 在开发web应用程序时,springboot会配置一个嵌入式Tomcat服务器,以便它可以作为独立的应 用程序运行。(Tomcat是默认的,当然你也可以配置Jetty或Undertow) SpringBoot包括许多有用的非功能特性(例如安全和健康检查)。

32、Spring Boot 自动配置原理

@Import + @Configuration + Spring spi 自动配置类由各个starter提供,使用@Configuration + @Bean定义配置类,放到META- INF/spring.factories下。 使用Spring spi扫描META-INF/spring.factories下的配置类。 使用@Import导入自动配置类。 详细流程原理分析,参考毕业精讲课。

33、Spring Boot中如何实现对不同环境的属性配置文件的

支持

Spring Boot支持不同环境的属性配置文件切换,通过创建application-{profile}.properties文件,其中 {profile}是具体的环境标识名称, 例如: application-dev.properties用于开发环境, application-test.properties用于测试环境, application-prod.properties用于prod环境。 如果要想使用application-dev.properties文件,则在application.properties文件中添加 spring.profiles.active=dev。 如果要想使用application-test.properties文件,则在application.properties文件中添加 spring.profiles.active=test。

34、Spring Boot 的核心注解是哪个?它主要由哪几个注

解组成的

启动类上面的注解是@SpringBootApplication,它也是 Spring Boot 的核心注解,主要组合包含了以下 3 个注解: @SpringBootConfiguration:组合了 @Configuration 注解,实现配置文件的功能。 @EnableAutoConfiguration:打开自动配置的功能,也可以关闭某个自动配置的选项,如关闭数据源 自动配置功能: @SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })。 @ComponentScan:Spring组件扫描。

35、你如何理解 Spring Boot 中的 Starter

使用spring + springmvc使用,如果需要引入mybatis等框架,需要到xml中定义mybatis需要的bean. starter就是定义一个starter的jar包,写一个@Configuration配置类、将这些bean定义在里面,然后在 starter包的META-INF/spring.factories中写入该配置类,springboot会按照约定来加载该配置类. 开发人员只需要将相应的starter包依赖进应用,进行相应的属性配置(使用默认配置时,不需要配 置),就可以直接进行代码开发,使用对应的功能了,比如mybatis-spring-boot—starter,spring- boot-starter-Redis.

36、Spring Boot Starter 的工作原理是什么

Spring Boot 在启动的时候会干这几件事情: Spring Boot 在启动时会去依赖的 Starter 包中寻找 resources/META-INF/spring.factories 文件, 然后根据文件中配置的 Jar 包去扫描项目所依赖的 Jar 包。 根据 spring.factories 配置加载 AutoConfigure 类 根据 @Conditional 注解的条件,进行自动配置并将 Bean 注入 Spring Context 总结一下,其实就是 Spring Boot 在启动的时候,按照约定去读取 Spring Boot Starter 的配置信息,再 根据配置信息对资源进行初始化,并注入到 Spring 容器中。这样 Spring Boot 启动完毕后,就已经准备 好了一切资源,使用过程中直接注入对应 Bean 资源即可.

37、什么是嵌入式服务器?为什么要使用嵌入式服务器

节省了下载安装tomcat,应用也不需要再打war包,然后放到webapp目录下再运行,只需要一个安装 了 Java 的虚拟机,就可以直接在上面部署应用程序了。springboot已经内置了tomcat.jar,运行main方 法时会去启动tomcat,并利用tomcat的spi机制加载springmvc

38、Spring Boot 的约定优于配置

  1. 首先,约定优于配置是一种软件设计的范式,它的核心思想是减少软件开发人员对于配置项的维 护,从而让开发人员更加聚焦在业务逻辑上。
  2. Spring Boot 就是约定优于配置这一理念下的产物,它类似于 Spring 框架下的一个脚手架,通过 Spring Boot,我们可以快速开发基于Spring 生态下的应用程序。
  3. 基于传统的Spring框架开发web 应用,我们需要做很多和业务开发无关并且只需要做一次的配置, 比如 a. 管理jar 包依赖 b. web.xml 维护 c. Dispatch-Servlet.xml 配置项维护 d. 应用部署到Web 容器 第三方组件集成到 Spring IOC 容器中的配置项维护,而在Spring Boot 中,我们不需要再去做这些繁琐 的配置,Spring Boot 已经自动帮我们完成了,这就是约定于配置思想的体现。
  4. Spring Boot 约定优于配置的体现有很多,比如 a. Spring Boot Starter 启动依赖,它能帮我们管理所有 jar 包版本 b. 如果当前应用依赖了 spring mvc 相关的jar,那么Spring Boot 会自动内置Tomcat 容器来运行 web 应用,我们不需要再去单独做应用部署。 c. Spring Boot 的自动装配机制的实现中,通过扫描约定路径下的 spring.factories 文件来识别配 置类,实现Bean 的自动装配。 d. 默认加载的配置文件 application.properties 等等。 总的来说,约定优于配置是一个比较常见的软件设计思想,它的核心本质都是为了更高效以及更便捷地 实现软件系统的开发和维护。 以上就是我对这个问题的理解。

39、spring cloud断路器的作用是什么

Spring Cloud断路器(Hystrix)是一种用于构建弹性、容错和容灾机制的开源库。它提供了一种通过隔 离和控制远程服务调用的方式,以防止由于服务故障或延迟而导致的级联故障。 断路器的作用如下:

  1. 故障隔离:断路器通过将远程服务调用封装在一个独立的断路器中,可以隔离故障的影响范围。当 远程服务发生故障或延迟时,断路器可以快速失败并返回预定义的默认值,而不会影响整个系统的 稳定性。
  2. 容错处理:断路器可以在远程服务不可用时提供备用方案。通过配置降级逻辑,可以在远程服务故 障时返回预先定义的备用数据,以保证系统的可用性和稳定性。
  3. 自动恢复:断路器具备自我修复的能力。它会定期尝试恢复远程服务的调用,以检查其可用性。当 远程服务恢复正常时,断路器会逐渐恢复对该服务的调用,并重新建立正常的调用链路。
  4. 实时监控:断路器提供了实时监控和度量功能,可以收集和展示远程服务调用的各项指标,如调用 次数、失败率、响应时间等。这些指标可以帮助开发人员和运维人员了解系统的健康状况,并进行 故障排查和性能优化。 通过使用Spring Cloud断路器,可以有效地处理分布式系统中的服务故障和延迟问题,提高系统的容错 性和可用性。它是构建弹性和可靠微服务架构的重要组件之一。

40、spring cloud的核心组件有哪些以及作用

Eureka:服务注册与发现 注册:每个服务都向Eureka登记自己提供服务的元数据,包括服务的ip地址、端口号、版本号、通信协 议等。eureka将各个服务维护在了一个服务清单中(双层Map,第一层key是服务名,第二层key是实例 名,value是服务地址加端口)。同时对服务维持心跳,剔除不可用的服务,eureka集群各节点相互注 册每个实例中都有一样的服务清单。 发现:eureka注册的服务之间调用不需要指定服务地址,而是通过服务名向注册中心咨询,并获取所有 服务实例清单(缓存到本地),然后实现服务的请求访问。 Ribbon:服务间发起请求的时候,基于Ribbon做负载均衡,从⼀个服务的多台机器中选择⼀台(被调 用方的服务地址有多个),Ribbon也是通过发起http请求,来进行的调用,只不过是通过调用服务名的 地址来实现的。虽然说Ribbon不用去具体请求服务实例的ip地址或域名了,但是每调用一个接口都还要 手动去发起Http请求。 Hystrix:发起请求是通过Hystrix的线程池来⾛的,不同的服务⾛不同的线程池,实现了不同服务调⽤ 的隔离,通过统计接口超时次数返回默认值,实现服务熔断和降级。 Zuul:如果前端、移动端要调⽤后端系统,统⼀从Zuul⽹关进⼊,由Zuul⽹关转发请求给对应的服务, 通过与Eureka进行整合,将自身注册为Eureka下的应用,从Eureka下获取所有服务的实例,来进行服务 的路由。Zuul还提供了一套过滤器机制,开发者可以自己指定哪些规则的请求需要执行校验逻辑,只有 通过校验逻辑的请求才会被路由到具体服务实例上,否则返回错误提示。 SpringCloud Config:提供服务器端和客户端。服务器存储后端的默认实现使用git,因此它轻松支持标 签版本的配置环境,以及可以访问用于管理内容的各种工具。这个还是静态的,需要得配合Spring Cloud Bus实现动态的配置更新。

41、spring 如何注册cloud服务

在Spring框架中使用Spring Cloud服务,需要进行以下步骤来注册和启用Spring Cloud组件:

  1. 添加依赖:在项目的构建配置文件(如pom.xml)中添加所需的Spring Cloud依赖。例如,可以添
加spring-cloud-starter-netflix-eureka-client 依赖来使用Eureka服务注册与发现组件。
  1. 配置文件:在应用程序的配置文件(如application.properties或application.yml)中配置Spring Cloud服务的相关属性。具体的配置内容取决于所使用的Spring Cloud组件。例如,使用Eureka服 务注册与发现时,需要指定Eureka服务器的地址和端口等信息。
  2. 注解标记:在Spring Boot应用程序的主类上添加相应的注解来启用Spring Cloud服务。具体的注 解取决于所使用的Spring Cloud组件。例如,使用Eureka服务注册与发现时,可以在主类上添加
@EnableEurekaClient 注解。
  1. 运行环境:确保应用程序运行的环境中包含了所需的Spring Cloud组件的运行实例。例如,如果使 用Eureka服务注册与发现,需要确保Eureka服务器正常运行。 下面是一个使用Eureka服务注册与发现的示例: 1 . 添加依赖: 2 . 配置文件(application.yml): 3 . 主类上添加注解: 通过以上步骤,你就可以在Spring应用程序中注册和启用Spring Cloud服务了。具体的步骤和配置内容 可能会根据所使用的Spring Cloud组件而有所不同,你可以根据具体的需求和文档进行相应的配置和操 作。

42、微服务优点是什么

(1)每个服务都足够内聚,代码易于理解; (2)提高开发效率,一项服务只做一件事; (3)微服务可以由小团队单独开发;

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
spring:
application:
name: my-service
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
importorg.springframework.boot.SpringApplication;
importorg.springframework.boot.autoconfigure.SpringBootApplication;
importorg.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication
@EnableEurekaClient
publicclassMyServiceApplication {
publicstaticvoidmain(String[] args) {
SpringApplication.run(MyServiceApplication.class, args);
}
}

(4)微服务是松耦合的,是有功能意义的服务; (5)可以用不同的语言开发,面向接口编程; (6)易于与第三方集成; (7)微服务只是业务逻辑的代码,不会和HTML一起使用。CSS或其它界面组合; (8)可灵活搭配,连接公共库和独立库。

43、微服务的缺点是什么

(1)分布式系统的责任; (2)多服务运维难度,随着服务的增加,运维压力也在增加; (3)系统部署依赖; (4)服务间通信成本; (5)数据一致性; (6)系统集成测试; (7)性能监控。

44、Spring Cloud Bus是什么

Spring Cloud Bus是Spring Cloud框架中的一个组件,用于在分布式系统中实现消息总线功能。它建立 在Spring Boot和Spring Cloud Stream之上,提供了一种方便的方式来在微服务架构中进行消息传递和 事件广播。 Spring Cloud Bus的主要功能是通过消息代理将分布式系统中的各个微服务实例连接起来,使它们能够 方便地进行消息的发送和接收。它使用轻量级的消息代理(如RabbitMQ或Kafka)作为中间件,实现了 消息的广播和订阅机制。 使用Spring Cloud Bus,可以实现以下功能:

  1. 配置中心的刷新:通过发送特定的消息,可以触发所有微服务实例重新加载配置信息,从而实现配 置的动态更新。
  2. 事件广播:可以将事件消息广播给所有微服务实例,用于实现系统内的事件驱动机制。
  3. 监控和管理:可以通过消息传递的方式,实现对微服务实例的监控和管理,例如获取实例的健康状 态、查看日志等。 Spring Cloud Bus使用了发布-订阅模式,其中一个微服务实例作为消息的生产者,将消息发送到消息代 理,而其他微服务实例作为消息的消费者,订阅感兴趣的消息并进行相应的处理。通过这种方式,可以 实现微服务之间的解耦和灵活的消息传递。 需要注意的是,Spring Cloud Bus并不是用于高频率的数据传输或大规模的消息通信,而是用于在分布 式系统中进行配置更新、事件广播和管理操作。对于更复杂的消息通信需求,可以结合使用Spring Cloud Stream等组件来实现。 总之,Spring Cloud Bus提供了一种简单而强大的方式来实现分布式系统中的消息总线功能,实现了微 服务之间的解耦和灵活的消息传递。它是构建基于Spring Cloud的分布式系统的重要工具之一。

45、什么是服务熔断?什么是服务降级

1、概念不同:

服务熔断:其实很好理解,就是一个断开的过程。 下游的服务因为某种原因突然变得不可用或响应过慢,上游服务为了保证自己整体服务的可用性,不再 继续调用目标服务,直接返回,快速释放资源。如果目标服务情况好转则恢复调用。 服务降级:降低级别的意思,它是指程序在出现问题时,仍能保证有限功能可用的一种机制。 因此:对于降级是一种退而求其次的选择,而熔断却是整体不可用。 2、触发原因不太一样: 服务熔断一般是某个服务(下游服务)故障引起,而服务降级一般是从整体负荷考虑。

3、管理层次不太一样:

服务熔断是一个框架层次的处理(每个服务都要考虑,无业务层级之分),服务降级是业务层次的处 理。(比如降级一般是从最非核心服务开始) 服务熔断是服务降级的一种特殊情况,他是防止服务雪崩而采取的措施。系统发生异常或者延迟或者流 量太大,都会触发该服务的服务熔断措施,链路熔断,返回兜底方法。这是对局部的一种保险措施。 服务降级是对系统整体资源的合理分配。区分核心服务和非核心服务。对某个服务的访问延迟时间、异 常等情况做出预估并给出兜底方法。这是一种全局性的考量,对系统整体负荷进行管理。 其实一句话:降级是一种设计思想,在Java层面就是一个接口,而熔断是降级的不同实现方式,在Java 层面就是这个接口的一个实现类。

4、熔断和降级的关系

46、负载均衡的意义是什么

负载均衡(Load Balancing)是指将网络或计算资源分配给多个服务器或设备,以达到提高系统性能、 可扩展性和可靠性的目的。它在分布式系统中起到重要的作用,具有以下几个重要的意义:

  1. 提高系统性能:负载均衡将请求均匀地分配给多个服务器,避免了单个服务器过载的情况。通过合 理地分配负载,可以充分利用系统的资源,提高系统的吞吐量和响应速度。
  2. 实现高可用性:通过将请求分发到多个服务器,即使其中某个服务器发生故障或不可用,仍然可以 继续提供服务。负载均衡器能够检测到故障服务器,并将请求转发到其他正常工作的服务器,从而 实现系统的高可用性和容错性。
  3. 支持系统扩展:随着用户量和业务需求的增加,单个服务器可能无法满足系统的需求。负载均衡器 可以根据实际情况动态地添加或删除服务器,实现系统的水平扩展。通过增加服务器数量,可以提 高系统的处理能力和并发性能。
  4. 优化资源利用:负载均衡器可以根据服务器的负载情况智能地分配请求,使每个服务器的负载相对 均衡。这样可以避免某些服务器过载而其他服务器处于空闲状态的情况,最大限度地提高资源的利 用率。
  5. 简化系统管理:通过使用负载均衡器,可以将多个服务器组织成一个逻辑集群,对外提供统一的入 口。这样可以简化系统的管理和维护工作,减少对客户端的影响,提高系统的可维护性和可管理 性。 总之,负载均衡在分布式系统中具有重要的意义。它能够提高系统性能、可用性和可扩展性,优化资源 利用,简化系统管理。通过合理地分配负载,负载均衡器能够实现高效、稳定和可靠的系统运行。

47、SpringBoot和SpringCloud有什么联系和区别

Spring Boot和Spring Cloud是两个相互关联但又有不同重点的项目。

  1. Spring Boot(Spring框架):Spring Boot是一个用于简化和加速Spring应用程序开发的框架。它 提供了一种约定优于配置的方式,通过自动配置和默认值,可以快速搭建和部署独立的、可执行的 Spring应用程序。Spring Boot使得开发者可以更专注于业务逻辑的实现,而无需手动配置复杂的 Spring配置文件。
  2. Spring Cloud:Spring Cloud是一个用于构建分布式系统的工具集合,它基于Spring Boot提供了 一系列的开箱即用的分布式系统模块和服务。Spring Cloud包含了多个组件,如服务注册与发现 (Eureka、Consul)、服务调用(Feign、Ribbon)、断路器(Hystrix)、网关(Zuul、 Gateway)、配置中心(Config)等,这些组件提供了分布式系统开发中常用的功能和解决方案。 联系和区别: 联系:Spring Boot是Spring框架的一部分,它为Spring应用程序提供了快速开发的能力,而 Spring Cloud是构建分布式系统的工具集合,基于Spring Boot提供了一系列分布式系统模块和服 务。 区别:Spring Boot主要关注于简化和加速单个Spring应用程序的开发,提供了自动配置、快速启 动等特性。而Spring Cloud主要关注于构建分布式系统,提供了服务注册与发现、服务调用、负载 均衡、断路器等组件,用于处理分布式系统中的各种挑战。 综上所述,Spring Boot和Spring Cloud是相互关联的,Spring Boot为Spring应用程序提供了快速开发 的能力,而Spring Cloud则在此基础上提供了构建分布式系统的工具和解决方案。使用Spring Boot可以 快速搭建单个Spring应用程序,而使用Spring Cloud可以在分布式系统中构建和管理多个相互协作的微 服务。

48、你对 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 是基于阿里巴巴开源组件集成的一套微服务解决方案,其中包括
  6. Dubbo——消息通讯
  7. Nacos——服务注册与发现
  8. Seata——事务隔离
  9. Sentinel——熔断降级 有了Spring Cloud 这样的技术生态,使得我们在落地微服务架构时。不用去考虑第三方技术集成带来额 外成本,只要通过配置组件来完成架构下的技术问题,从而可以让我们更加侧重性能方面

以上这些就是我对 Spring Cloud 的个人理解!

三、MyBatis

1、Mybatis中#和$的区别

在MyBatis中,# 和$ 是两种不同的参数占位符使用方式。

  1. 占位符:# 占位符是使用预编译的方式进行参数传递。在SQL语句中,使用# 可以将参数值安全

地替换到SQL语句中,并自动进行参数类型转换和防止SQL注入攻击。使用# 占位符时,MyBatis 会将参数值作为一个整体传递给数据库,因此可以有效地防止SQL注入问题。 例如:

SELECT * FROM users WHERE id = #{userId}

在上述示例中,#{userId} 会被替换为具体的参数值,并且会根据参数类型进行合适的转换。 2. $ 占位符:$ 占位符是使用字符串拼接的方式进行参数传递。在SQL语句中,使用$ 可以将参数值 直接拼接到SQL语句中,不进行预编译和参数类型转换。使用$ 占位符时,需要注意潜在的安全风 险,因为参数值直接拼接到SQL语句中,可能会导致SQL注入攻击。 例如:

SELECT * FROM users WHERE id = ${userId}

在上述示例中,${userId} 会被替换为具体的参数值,但不会进行参数类型转换和安全检查。 总结: 使用# 占位符可以提供更安全和可靠的参数传递方式,适用于大多数情况。 使用$ 占位符可以提供更灵活的参数传递方式,但需要注意安全性和潜在的SQL注入问题。 在选择使用# 还是$ 时,需要根据具体的需求和安全考虑来决定。一般来说,推荐使用# 占位符,除非 有特殊的需求需要使用$ 占位符。

2、Mybatis的编程步骤是什么样的

●创建SqlSessionFactory ●通过SqlSessionFactory创建SqlSession ●通过sqlsession执行数据库操作 ●调用session.commit()提交事务 ●调用session.close()关闭会话

3、JDBC编程有哪些不足之处,MyBatis是如何解决这些问

题的

JDBC编程在某些方面存在一些不足之处,而MyBatis通过提供一种更高级的抽象层来解决这些问题。以 下是JDBC编程的一些不足之处以及MyBatis是如何解决这些问题的:

  1. 繁琐的代码:JDBC编程需要编写大量的重复代码,包括连接数据库、创建和释放资源、处理结果集 等。这使得代码冗长、难以维护和理解。 MyBatis通过提供一个简洁的配置文件和映射文件,将数据库操作的细节抽象出来,使得开发 者只需关注SQL语句和参数映射,大大减少了繁琐的代码量。
  2. SQL与Java代码的耦合:在JDBC编程中,SQL语句通常直接嵌入在Java代码中,导致SQL与Java代 码紧密耦合,不易于维护和修改。 MyBatis使用了面向SQL的思想,将SQL语句与Java代码分离,通过映射文件将SQL语句与Java 对象进行映射,使得SQL与Java代码解耦,提高了代码的可维护性和可读性。
  3. 手动参数设置和结果集处理:在JDBC编程中,需要手动设置参数并处理结果集,包括类型转换、结 果集遍历等,增加了开发的工作量和出错的可能性。 MyBatis通过提供参数映射和结果集映射的功能,自动处理参数设置和结果集转换,开发者只 需定义映射关系,MyBatis会自动完成参数设置和结果集处理,简化了开发过程。
  4. 缺乏对象关系映射(ORM)支持:JDBC编程需要手动将数据库查询结果映射到Java对象中,缺乏 对对象关系映射的支持,增加了开发的复杂性。 MyBatis提供了强大的对象关系映射(ORM)功能,可以将查询结果自动映射到Java对象中, 支持一对一、一对多、多对一等复杂的关系映射,简化了数据操作的过程。
  5. 缺乏缓存支持:JDBC编程没有内置的缓存机制,每次查询都需要访问数据库,降低了性能。 MyBatis提供了一级缓存和二级缓存的支持。一级缓存是在同一个会话中的缓存,可以减少对 数据库的访问;二级缓存是在多个会话中的缓存,可以共享缓存结果,提高了查询性能。 总的来说,MyBatis通过提供简洁的配置和映射文件、解耦SQL与Java代码、自动处理参数和结果集、支 持对象关系映射和缓存等功能,弥补了JDBC编程的不足之处,提供了更便捷、高效和可维护的数据库访 问解决方案。

4、使用MyBatis的mapper接口调用时有哪些要求

● Mapper接口方法名和mapper.xml中定义的每个sql的id相同 ● Mapper接口方法的输入参数类型和mapper.xml中定义的每个sql的parameterType的类型相同 ● Mapper接口方法的输出参数类型和mapper.xml中定义的每个sql的resultType的类型相同 ● Mapper.xml文件中的namespace即是mapper接口的类路径。

5、Mybatis中一级缓存与二级缓存

在 MyBatis 中,存在一级缓存和二级缓存两种缓存机制。

  1. 一级缓存: 一级缓存是 MyBatis 默认开启的缓存机制,也被称为本地缓存。 一级缓存的作用范围是在同一个 SqlSession 内部,即在同一个会话期间,多次执行相同的查 询语句,第一次查询结果会被缓存到一级缓存中,后续的查询会直接从缓存中获取结果,而不 再去查询数据库。 一级缓存是基于对象引用的方式实现的,因此在同一个会话期间,如果对查询结果进行了修改 (例如更新、插入、删除等操作),则会清空一级缓存,以保证数据的一致性。
  2. 二级缓存: 二级缓存是 MyBatis 的全局缓存机制,作用范围是在同一个 Mapper 的 namespace 中,即 多个 SqlSession 共享同一个 Mapper 的缓存。 二级缓存可以跨越多个会话,当多个会话执行相同的查询语句时,第一个会话的查询结果会被 缓存到二级缓存中,后续的会话可以直接从缓存中获取结果,而不再去查询数据库。 二级缓存是基于序列化的方式实现的,因此要求缓存的对象必须是可序列化的。 默认情况下,二级缓存是关闭的,需要在 Mapper 的配置文件中显式配置开启。 需要注意的是: 一级缓存和二级缓存是独立的,互不影响。 二级缓存是可选的,可以根据需要选择是否开启。 二级缓存的使用需要注意数据的一致性和并发访问的问题,特别是在多线程环境下。 总结: 一级缓存是在同一个会话期间的缓存,而二级缓存是在同一个 Mapper 的命名空间中的缓存。一级缓存 是默认开启的,二级缓存是可选的。在实际应用中,可以根据需求选择合适的缓存机制来提高查询性 能。

6、MyBatis在insert插入操作时如何返回主键ID

在 MyBatis 中,可以通过以下几种方式来获取插入操作后生成的主键 ID:

  1. 使用数据库的自增主键: 如果你的表使用了数据库的自增主键(如 MySQL 的 AUTO_INCREMENT),则在执行插入操
作后,可以通过SELECT LAST_INSERT_ID()来获取最后插入的主键 ID。

在 MyBatis 的映射文件(Mapper XML)中,可以使用元素来配置获取主键 的语句,例如: 2. 使用数据库的序列(Sequence): 如果你的表使用了数据库的序列(如 Oracle 的序列),则可以通过调用序列的NEXTVAL函 数来获取下一个序列值作为主键 ID。 在 MyBatis 的映射文件中,可以使用元素来配置获取序列值的语句,例如: 3. 使用数据库的触发器(Trigger): 如果你的表使用了数据库的触发器,在触发器中可以获取插入操作后生成的主键 ID,并将其 设置到对应的列中。 在 MyBatis 中,执行插入操作后,可以通过查询插入的记录来获取主键 ID,例如:

<insertid="insertUser"parameterType="User">
<selectKeykeyProperty="id"resultType="Long"order="AFTER">
SELECT LAST_INSERT_ID()
</selectKey>
INSERT INTO user (username, password) VALUES (#{username}, #{password})
</insert>
  • 在执行插入操作后,MyBatis 会自动执行 <selectKey> 中配置的语句,并将获取到的主键值设置到
对应的属性(`keyProperty`)中。
<insertid="insertUser"parameterType="User">
<selectKeykeyProperty="id"resultType="Long"order="BEFORE">
SELECT user_seq.NEXTVAL FROM DUAL
</selectKey>
INSERT INTO user (id, username, password) VALUES (#{id}, #{username}, #
{password})
</insert>
  • 在执行插入操作前,MyBatis 会先执行 <selectKey> 中配置的语句,并将获取到的序列值设置到对
应的属性(`keyProperty`)中。

根据你所使用的数据库和表的配置,选择适合的方式来获取插入操作后的主键 ID。

7、简述 Mybatis 的插件运行原理,如何编写一个插件

答: Mybatis 只支持针对 ParameterHandler、ResultSetHandler、StatementHandler、Executor 这 4 种接口的插件, Mybatis 使用 JDK 的动态代理,为需要拦截的接口生成代理对象以实现接口方法拦截 功能,每当执行这 4 种接口对象的方法时,就会进入拦截方法,具体就是 InvocationHandler 的 invoke() 方法,拦截那些你指定需要拦截的方法。 编写插件:实现 Mybatis 的 Interceptor 接口并复写 intercept()方法,然后在给插件编写注解,指定 要拦截哪一个接口的哪些方法即可,在配置文件中配置编写的插件。

四、Redis

1、Redis是AP的还是CP的

Redis是一个支持多种数据结构的内存数据库,它可以根据配置和使用方式在AP和CP之间做出选择。具 体来说,Redis可以在不同的场景下提供不同的一致性级别:

  1. 在默认情况下,Redis追求最高的性能和可用性,更倾向于AP模型(即可用性优先)。它使用主从 复制和哨兵机制来实现高可用性,但在出现网络分区或节点故障时,可能会导致数据的不一致性。
  2. 但是,Redis也提供了一些支持一致性的特性,例如Redis Cluster和Redis Sentinel。通过使用这些 特性,Redis可以在需要更高一致性的场景下选择CP模型(即一致性优先)。在Redis Cluster中, 数据被分片存储在不同的节点上,并使用Gossip协议来保持数据的一致性。Redis Sentinel则提供 了监控和自动故障转移的功能,以保证高可用性和数据的一致性。 因此,根据具体的配置和使用方式,Redis可以在AP和CP之间进行选择。

2、介绍一下Redis的集群方案

1、主从模式:

// 执行插入操作

sqlSession.insert("insertUser", user);

// 获取插入后的主键 ID

Longid=user.getId();
@Intercepts({@Signature(type=StatementHandler.class, method="query", args=
{Statement.class, ResultHandler.class}),
@Signature(type=StatementHandler.class, method="update", args=
{Statement.class}),
@Signature(type=StatementHandler.class, method="batch", args= {
Statement.class })})
@Component
invocation.proceed()执行具体的业务逻辑

2、哨兵模式: sentinel,哨兵是 Redis 集群中非常重要的一个组件,主要有以下功能: 集群监控:负责监控 Redis master 和 slave 进程是否正常工作。 消息通知:如果某个 Redis 实例有故障,那么哨兵负责发送消息作为报警通知给管理员。 故障转移:如果 master node 挂掉了,会自动转移到 slave node 上。 配置中心:如果故障转移发生了,通知 client 客户端新的 master 地址。 哨兵用于实现 Redis 集群的高可用,本身也是分布式的,作为一个哨兵集群去运行,互相协同工作。 故障转移时,判断一个 master node 是否宕机了,需要大部分的哨兵都同意才行,涉及到了分布 式选举 即使部分哨兵节点挂掉了,哨兵集群还是能正常工作的 哨兵通常需要 3 个实例,来保证自己的健壮性 哨兵 + Redis 主从的部署架构,是不保证数据零丢失的,只能保证 Redis 集群的高可用性。 对于哨兵 + Redis 主从这种复杂的部署架构,尽量在测试环境和生产环境,都进行充足的测试和演 练。

3、Redis Cluster模式:

Redis Cluster是一种服务端Sharding技术,3.0版本开始正式提供。采用slot(槽)的概念,一共分成 16384个槽。将请求发送到任意节点,接收到请求的节点会将查询请求发送到正确的节点上执行。 方案说明: 通过哈希的方式,将数据分片,每个节点均分存储一定哈希槽(哈希值)区间的数据,默认分配了 16384 个槽位 每份数据分片会存储在多个互为主从的多节点上 数据写入先写主节点,再同步到从节点(支持配置为阻塞同步) 同一分片多个节点间的数据不保持强一致性 读取数据时,当客户端操作的key没有分配在该节点上时,Redis会返回转向指令,指向正确的节点 扩容时需要需要把旧节点的数据迁移一部分到新节点 在 Redis Cluster 架构下,每个 Redis 要放开两个端口号,比如一个是 6379,另外一个就是加1w 的端 口号,比如 16379。 16379 端口号是用来进行节点间通信的,也就是 cluster bus 的通信,用来进行故障检测、配置更新、 故障转移授权。cluster bus 用了另外一种二进制的协议,gossip 协议,用于节点间进行高效的数据交 换,占用更少的网络带宽和处理时间。

优点:

无中心架构,支持动态扩容,对业务透明 具备Sentinel的监控和自动Failover(故障转移)能力 客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可 高性能,客户端直连Redis服务,免去了proxy代理的损耗

缺点:

运维也很复杂,数据迁移需要人工干预 只能使用0号数据库 不支持批量操作(pipeline管道操作) 分布式逻辑和存储模块耦合等 Redis Sharding是Redis Cluster出来之前,业界普遍使用的多Redis实例集群方法。其主要思想是采用 哈希算法将Redis数据的key进行散列,通过hash函数,特定的key会映射到特定的Redis节点上。Java Redis客户端驱动jedis,支持Redis Sharding功能,即ShardedJedis以及结合缓存池的 ShardedJedisPool 优点: 优势在于非常简单,服务端的Redis实例彼此独立,相互无关联,每个Redis实例像单服务器一样运行, 非常容易线性扩展,系统的灵活性很强 缺点: 由于sharding处理放到客户端,规模进一步扩大时给运维带来挑战。 客户端sharding不支持动态增删节点。服务端Redis实例群拓扑结构有变化时,每个客户端都需要更新调 整。连接不能共享,当应用规模增大时,资源浪费制约优化

3、什么是Redis的数据分片

Redis的数据分片是一种数据分布在多个节点上的技术,用于实现水平扩展和负载均衡。在Redis中,数 据分片是通过哈希槽(Hash Slot)来实现的。 具体而言,数据分片的过程如下:

  1. 哈希槽的定义:Redis将整个数据空间划分为固定数量的哈希槽,通常是16384个。每个哈希槽都 有一个唯一的标识符,从0到16383。
  2. 数据的映射:当客户端发送一个命令请求时,Redis Cluster通过对的哈希值进行计算,将键值对映 射到一个特定的哈希槽中。这样,每个键值对就被分配到了一个特定的哈希槽中。
  3. 哈希槽的分配:Redis Cluster将所有的哈希槽均匀地分配给各个节点,每个节点负责存储一部分哈 希槽对应的数据。这样,数据就被分片存储在了多个节点上。
  4. 数据的查找:当客户端需要访问某个键值对时,它首先计算键的哈希值,然后根据哈希值找到对应 的哈希槽。客户端根据哈希槽的信息找到负责该哈希槽的节点,并将请求发送给该节点。
  5. 数据的迁移:当需要添加或删除节点时,Redis Cluster会进行数据的迁移,以保持各个节点负载均 衡。数据迁移的过程中,哈希槽会从一个节点移动到另一个节点,保证数据的分片均匀和一致。通 过数据分片,Redis可以在多个节点上并行处理请求,提高了系统的吞吐量和容量。 同时,数据分片还实现了负载均衡和故障隔离,当某个节点故障时,其他节点仍然可以继续提供服务。 总的来说,Redis的数据分片通过哈希槽的方式,将数据分布在多个节点上,实现了水平扩展、负载均衡 和故障隔离。

4、Redis为什么这么快

Redis之所以被认为是快速的,主要有以下几个原因:

  1. 内存存储:Redis将数据存储在内存中,而不是磁盘上。相比于磁盘访问内存访问速度更快,可以 实现很低的延迟和高吞吐量。
  2. 单线程模型:Redis采单线程模型,避免了多线程间的竞争和上下文切换的开销。线程模型简化了 并发控制,减少了锁的使用,提高了处理请求的效率。
  3. 高效的数据结构:Redis提供了多种高效的数据结构,如字符串、哈希表、跳跃表、集合和有序集 合等。这些数据结构在内部实现上都经过了优化,能够快速地进行插入、删除、查找和遍历操作。
  4. 异步操作:Redis支持异步操作,可以在后台执行一些耗时的操作,如持久化、复制和集群的同步 等。这样可以减少客户端的等待时间,提高系统的响应速度。
  5. 高效的网络通信(核心):Redis自定义的RESP协议进行网络通信,协议本身简单而高效。Redis 的网络通信采用非阻塞I/O多路复用机制和事件驱动的方式,可以处理大量的并发连接,提高了系 统的并发性能。
  6. 优化的算法和数据结构:Redis在内部实现中使用了许多优化的算法和数据结构。例如,使用跳跃 表(Skip List)来实现有序集合,使用压缩列表(ziplist)来存储小规模的列表和哈希表等。这些 优化可以减少内存占用和提高数据操作的效率。 总的来说,Redis之所以快速,是因为它使用内存存储、采用单线程模型、提供高效的数据结构、支持异 步操作、优化网络通信和使用优化的算法和数据结构等。这些特性使得能够在处理大量请求时保持低延 迟和高吞吐量。

5、Redis 的事务机制是怎样的

1、事务开始 MULTI命令的执行,标识着一个事务的开始。MULTI命令会将客户端状态的 flags 属性中打开 Redis_MULTI 标识来完成的。 2、命令入队 当一个客户端切换到事务状态之后,服务器会根据这个客户端发送来的命令来执行不同的操作。如果客 户端发送的命令为MULTI、EXEC、WATCH、DISCARD中的一个,立即执行这个命令,否则将命令放入 一个事务队列里面,然后向客户端返回 QUEUED 回复 如果客户端发送的命令为 EXEC、DISCARD、WATCH、MULTI 四个命令的其中一个,那么服务器 立即执行这个命令。 如果客户端发送的是四个命令以外的其他命令,那么服务器并不立即执行这个命令。 首先检查此命令的格式是否正确,如果不正确,服务器会在客户端状态(RedisClient)的 flags 属 性关闭 Redis_MULTI 标识,并且返回错误信息给客户端。 如果正确,将这个命令放入一个事务队列里面,然后向客户端返回 QUEUED 回复

3、事务执行

客户端发送 EXEC 命令,服务器执行 EXEC 命令逻辑。 如果客户端状态的 flags 属性不包含 Redis_MULTI 标识,或者包含 Redis_DIRTY_CAS 或者 Redis_DIRTY_EXEC 标识,那么就直接取消事务的执行。 否则客户端处于事务状态(flags 有 Redis_MULTI 标识),服务器会遍历客户端的事务队列,然后 执行事务队列中的所有命令,最后将返回结果全部返回给客户端; Redis 不支持事务回滚机制,但是它会检查每一个事务中的命令是否错误。 Redis 事务不支持检查那些程序员自己逻辑错误。例如对 String 类型的数据库键执行对HashMap 类型 的操作! WATCH命令是一个乐观锁,可以为 Redis 事务提供 check-and-set (CAS)行为。可以监控一个或多个 键,一旦其中有一个键被修改(或删除),之后的事务就不会执行,监控一直持续到EXEC命令。 MULTI命令用于开启一个事务,它总是返回OK。MULTI执行之后,客户端可以继续向服务器发送任意多 条命令,这些命令不会立即被执行,而是被放到一个队列中,当EXEC命令被调用时,所有队列中的命令 才会被执行。 EXEC:执行所有事务块内的命令。返回事务块内所有命令的返回值,按命令执行的先后顺序排 列。当操作被打断时,返回空值 nil 。 通过调用DISCARD,客户端可以清空事务队列,并放弃执行事务,并且客户端会从事务状态中退 出。 UNWATCH命令可以取消watch对所有key的监控

6、介绍下Redis的主从复制模式

Redis的主从复制(Master-Slave Replication)是一种常见的架构模式,用于在不同Redis实例之间同步 数据。主从模式允许一个Redis实例(称为主节点)将其数据复制到一个或多个其他Redis实例(称为从 节点),从而实现数据的备份、读写分离、扩展性以及高可用性。 主从模式的工作原理:

  1. 角色定义: 主节点(Master):主节点是数据的源头,负责接收写入请求并处理客户端的读写操作。 从节点(Slave):从节点通过复制主节点的数据来保持数据的同步,并且可以用于读取操 作。
  2. 初始化同步: 当一个从节点与主节点建立连接时,它会发送一个同步命令给主节点,请求复制数据。 主节点接受到请求后,执行一个后台的快照操作(类似于RDB持久化),将数据库状态保存到 一个RDB文件中。 主节点将这个RDB文件发送给从节点,并在发送过程中继续记录执行的写命令。
  3. 增量复制: 一旦从节点完成初始数据同步,主节点将继续将新的写命令发送给从节点。 从节点接收到这些写命令并应用到自己的数据库中,保持与主节点的数据状态同步。 从节点定期向主节点发送心跳请求以确认连接是否存活,并在需要时重新同步数据。

7、Redis的持久化机制是怎样的

Redis提供了两种方式的持久化机制,分别是RDB快照和AOF日志。 RDB:Redis DataBase 在指定的时间间隔内将内存中的数据集快照写入磁盘,实际操作过程是fork一个子进程,先将数据集写 入临时文件,写入成功后,再替换之前的文件,用二进制压缩存储。

优点:

1、整个Redis数据库将只包含一个文件 dump.rdb,方便持久化。 2、容灾性好,方便备份。 3、性能最大化,fork 子进程来完成写操作,让主进程继续处理命令,所以是 IO 最大化。使用单独子进 程来进行持久化,主进程不会进行任何 IO 操作,保证了 Redis 的高性能 4.相对于数据集大时,比 AOF 的启动效率更高。

缺点:

1、数据安全性低。RDB 是间隔一段时间进行持久化,如果持久化之间 Redis 发生故障,会发生数据丢 失。所以这种方式更适合数据要求不严谨的时候) 2、由于RDB是通过fork子进程来协助完成数据持久化工作的,因此,如果当数据集较大时,可能会导致 整个服务器停止服务几百毫秒,甚至是1秒钟。

比较

AOF

RDB

默认选择 No Yes 数据格式 RESP格式所有写命令 压缩的二进制文件 数据的安全性 三种写回策略决定 分钟级别数据的丢失 文件大小 大 小 恢复速度 慢 快 重写机制 Yes No 重写阻塞主线 No No Redis的性能 三种写回策略决定 分钟级别的快照 数据恢复的选择 Yes No AOF:Append Only File 以日志的形式记录服务器所处理的每一个写、删除操作,查询操作不会记录,以文本的方式记录,可以 打开文件看到详细的操作记录。

优点:

1、数据安全,Redis中提供了3种同步策略,即每秒同步、每修改同步和不同步。事实上,每秒同步也 是异步完成的,其效率也是非常高的,所差的是一旦系统出现宕机现象,那么这一秒钟之内修改的数据 将会丢失。而每修改同步,我们可以将其视为同步持久化,即每次发生的数据变化都会被立即记录到磁 盘中。。 2、通过 append 模式写文件,即使中途服务器宕机也不会破坏已经存在的内容,可以通过 Redis- check-aof 工具解决数据一致性问题。 3、AOF 机制的 rewrite 模式。定期对AOF文件进行重写,以达到压缩的目的

缺点:

1、AOF 文件比 RDB 文件大,且恢复速度慢。 2、数据集大的时候,比 rdb 启动效率低。 3、运行效率没有RDB高。

AOF和RDB对比:

AOF文件比RDB更新频率高,优先使用AOF还原数据。 AOF比RDB更安全也更大 RDB性能比AOF 如果两个都配了优先加载AOF

8、Redis 的过期策略是怎么样的

Redis是key-value数据库,我们可以设置Redis中缓存的key的过期时间。Redis的过期策略就是指当 Redis中缓存的key过期了,Redis如何处理。 惰性过期:只有当访问一个key时,才会判断该key是否已过期,过期则清除。该策略可以最大化地节省 CPU资源,却对内存非常不友好。极端情况可能出现大量的过期key没有再次被访问,从而不会被清除, 占用大量内存。 定期过期:每隔一定的时间,会扫描一定数量的数据库的expires字典中一定数量的key,并清除其中已 过期的key。该策略是一个折中方案。通过调整定时扫描的时间间隔和每次扫描的限定耗时,可以在不同 情况下使得CPU和内存资源达到最优的平衡效果。 (expires字典会保存所有设置了过期时间的key的过期时间数据,其中,key是指向键空间中的某个键的 指针,value是该键的毫秒精度的UNIX时间戳表示的过期时间。键空间是指该Redis集群中保存的所有 键。) Redis中同时使用了惰性过期和定期过期两种过期策略。

9、Redis的内存淘汰策略是怎么样的

Redis 4.0 之前一共实现了 6 种内存淘汰策略,在 4.0 之后,又增加了 2 种策略。我们可以按照是否会进 行数据淘汰把它们分成两类: 不进行数据淘汰的策略,只有 noeviction 这一种。 会进行淘汰的 7 种其他策略。 会进行淘汰的 7 种策略,再进一步根据淘汰候选数据集的范围把它们分成两类: 在设置了过期时间的数据中进行淘汰,包括 volatile-random、volatile-ttl、volatile-lru、volatile- lfu(Redis 4.0 后新增)四种。 在所有数据范围内进行淘汰,包括 allkeys-lru、allkeys-random、allkeys-lfu(Redis 4.0 后新增) 三种。 具体解释下各个策略: 默认情况下,Redis 在使用的内存空间超过 maxmemory 值时,并不会淘汰数据,也就是设定的 noeviction 策略。对应到 Redis 缓存,也就是指,一旦缓存被写满了,再有写请求来时,Redis 不再提 供服务,而是直接返回错误。Redis 用作缓存时,实际的数据集通常都是大于缓存容量的,总会有新的 数据要写入缓存,这个策略本身不淘汰数据,也就不会腾出新的缓存空间,我们不把它用在 Redis 缓存 中。 接着 volatile-random、volatile-ttl、volatile-lru 和 volatile-lfu 这四种淘汰策略。它们筛选的候选数据 范围,被限制在已经设置了过期时间的键值对上。也正因为此,即使缓存没有写满,这些数据如果过期 了,也会被删除。 比如我们使用 EXPIRE 命令对一批键值对设置了过期时间后,无论是这些键值对的过期时间是快到了, 还是 Redis 的内存使用量达到了 maxmemory 阈值,Redis 都会进一步按照 volatile-ttl、volatile- random、volatile-lru、volatile-lfu 这四种策略的具体筛选规则进行淘汰 volatile-ttl 在筛选时,会针对设置了过期时间的键值对,根据过期时间的先后进行删除,越早过期 的越先被删除。 volatile-random 就像它的名称一样,在设置了过期时间的键值对中,进行随机删除。 volatile-lru 会使用 LRU 算法筛选设置了过期时间的键值对。 volatile-lfu 会使用 LFU 算法选择设置了过期时间的键值对。 相对于 volatile-ttl、volatile-random、volatile-lru、volatile-lfu 这四种策略淘汰的是设置了过期时间的 数据,allkeys-lru、allkeys-random、allkeys-lfu 这三种淘汰策略的备选淘汰数据范围,就扩大到了所 有键值对,无论这些键值对是否设置了过期时间。它们筛选数据进行淘汰的规则是: allkeys-random 策略,从所有键值对中随机选择并删除数据 allkeys-lru 策略,使用 LRU 算法在所有数据中进行筛选。 allkeys-lfu 策略,使用 LFU 算法在所有数据中进行筛选。 这也就是说,如果一个键值对被删除策略选中了,即使它的过期时间还没到,也需要被删除。当然,如 果它的过期时间到了但未被策略选中,同样也会被删除。

10、Redis的LRU算法

LRU 算法的全称是 Least Recently Used,从名字上就可以看出,这是按照最近最少使用的原则来筛选 数据,最不常用的数据会被筛选出来,而最近频繁使用的数据会留在缓存中。 那具体是怎么筛选的呢?LRU 会把所有的数据组织成一个链表,链表的头和尾分别表示 MRU 端和 LRU 端,分别代表最近最常使用的数据和最近最不常用的数据。 我们现在有数据 6、3、9、20、5。如果数据 20 和 3 被先后访问,它们都会从现有的链表位置移到 MRU 端,而链表中在它们之前的数据则相应地往后移一位。因为,LRU 算法选择删除数据时,都是从 LRU 端开始,所以把刚刚被访问的数据移到 MRU 端,就可以让它们尽可能地留在缓存中。 如果有一个新数据 15 要被写入缓存,但此时已经没有缓存空间了,也就是链表没有空余位置了,那 么,LRU 算法做两件事: 数据 15 是刚被访问的,所以它会被放到 MRU 端; 算法把 LRU 端的数据 5 从缓存中删除,相应的链表中就没有数据 5 的记录了。 其实,LRU 算法背后的想法非常朴素:它认为刚刚被访问的数据,肯定还会被再次访问,所以就把它放 在 MRU 端;长久不访问的数据,肯定就不会再被访问了,所以就让它逐渐后移到 LRU 端,在缓存满 时,就优先删除它。 不过,LRU 算法在实际实现时,需要用链表管理所有的缓存数据,这会带来额外的空间开销。而且,当 有数据被访问时,需要在链表上把该数据移动到 MRU 端,如果有大量数据被访问,就会带来很多链表 移动操作,会很耗时,进而会降低 Redis 缓存性能。 所以,在 Redis 中,LRU 算法被做了简化,以减轻数据淘汰对缓存性能的影响,Redis 默认会记录每个 数据的最近一次访问的时间戳(由键值对数据结构 RedisObject 中的 lru 字段记录)。然后,Redis 在 决定淘汰的数据时,第一次会随机选出 N 个数据,把它们作为一个候选集合。接下来,Redis 会比较这 N 个数据的 lru 字段,把 lru 字段值最小的数据从缓存中淘汰出去。 当需要再次淘汰数据时,Redis 需要挑选数据进入第一次淘汰时创建的候选集合。这儿的挑选标准是: 能进入候选集合的数据的 lru 字段值必须小于候选集合中最小的 lru 值。当有新数据进入候选数据集后, 如果候选数据集中的数据个数达到了 maxmemory-samples,Redis 就把候选数据集中 lru 字段值最小 的数据淘汰出去。 这样一来,Redis 缓存不用为所有的数据维护一个大链表,也不用在每次数据访问时都移动链表项,提 升了缓存的性能。

11、Redis的淘汰策略的选择

建议:

优先使用 allkeys-lru 策略。这样,可以充分利用 LRU 这一经典缓存算法的优势,把最近最常访问的数据 留在缓存中,提升应用的访问性能。如果你的业务数据中有明显的冷热数据区分,我建议你使用 allkeys-lru 策略。如果业务应用中的数据访问频率相差不大,没有明显的冷热数据区分,建议使用 allkeys-random 策略,随机选择淘汰的数据就行。 如果你的业务中有置顶的需求,比如置顶新闻、置顶视频,那么,可以使用 volatile-lru 策略,同时不给 这些置顶数据设置过期时间。这样一来,这些需要置顶的数据一直不会被删除。

12、什么是热Key问题,如何解决热key问题

热Key问题是指在Redis中,某个或某些特定的键被频繁访问,导致对这些键的操作成为系统的性能瓶 颈。热Key问题可能会导致Redis负载过高,响应时间延长,甚至造成系统崩溃。 为了解决热Key问题,可以采取以下一些策略:

  1. 增加缓存容量:扩大Redis内存容量可以提高缓存命中率,减少对热Key的访问次数,从而缓解热 Key问题。可以考虑升级Redis服务器或增加Redis集群节点来增加内存容量。
  2. 分片:将热Key均匀地分散到多个Redis实例中,每个实例处理一部分热Key的请求。这样可以降低 单个Redis实例的负载压力,提系统整体的吞吐量。
  3. 缓存预热:在系统启动或流量低峰期,提前加载热Key的数据到缓存中,使得这些热Key的数据在 实际访问时已经存在于缓存中,减少对后端存储的访问。
  4. 数据分片:对于热Key所对的数据量较大的情况,可以考虑将数据进行分片存储,将不同片段的数 据存放在不同的键上,从而减轻单个键的访问压力。
  5. 缓存降级:对于某些热Key,可以考虑将其缓存时间设置较短,或者不缓存,直接访问后端存储。 这样可以避免热Key对缓存系统的过度压力,同时确保其他非热Key正常缓存。
  6. 使用Redis集群:通过搭建Redis集群,将热Key分散到多个节点上,实现负载均衡和高可用性,提 高系统的整体性能和稳定性。 综合考虑具体业务场景和系统需求,可以采取不同的策略或组合使用来解决热Key问题。

13、什么是大Key问题,如何解决

大Key问题是指在Redis数据库中存储了过大的数据结构,如大型列表、哈希、集合等,导致内存占用过 高,影响Redis性能的情况。这可能会导致以下问题: 内存占用过高:大Key占用了大量的内存空间,导致其他数据无法被缓存,从而影响Redis的性能和响应 速度。 数据加载时间延长:由于大Key的加载和传输需要较长时间,会导致读取和写入操作的延迟。 持久化问题:在进行数据持久化(如RDB快照、AOF日志)时,大Key会增加持久化的时间和磁盘空间 占用。 解决大Key问题的方法包括: 数据分片:将大数据拆分成多个小数据,分别存储在不同的Key中。例如,将一个大型哈希表分成多个 小型哈希表。 使用分布式存储:将大数据存储在分布式数据库中,如Redis Cluster或其他分布式存储系统。 合理选择数据结构:根据实际需求,选择合适的数据结构,避免在单个Key中存储过大的数据。 数据压缩:对于可以压缩的数据,使用Redis提供的数据压缩功能,减小存储占用。 使用大Key分析工具: Redis提供了一些工具可以用于识别和处理大Key问题,例如Redis内存分析工具 和命令。 定期清理:周期性地检查数据库,发现并处理大Key,例如将大Key转移至其他存储系统。 合理设置过期时间:对于不再需要的数据,设置适当的过期时间,让Redis可以自动淘汰这些数据。 综合使用这些方法可以有效地解决大Key问题,提高Redis的性能和稳定性。

14、什么是缓存击穿、缓存穿透、缓存雪崩

击穿、缓存穿透和缓存雪崩都是与缓存相关的常见问题现象。 缓存击穿:是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多, 同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力。 解决方案: 设置热点数据永远不过期。 加互斥锁 缓存穿透:是指缓存和数据库中都没有的数据,导致所有的请求都落到数据库上,造成数据库短时间内 承受大量请求而崩掉。

解决方案:

1)固定值攻击:缓存一个空对象key-null或者任意一个数据,比如缓存key-”x” 2)随机值攻击: 1、接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截; 2、全量缓存:将数据库的数据全部缓存到Redis。 3、缓存id: 将数据库全部数据的id(id要具有唯一性)全部缓存到Redis。 4、布隆过滤器:在项目一启动的时,将数据库中所有的数据全部缓存到布隆过滤器(本地或者分布式版 本都可以,根据服务的实例数决定)中。布隆过滤器一个足够大的 bitmap ,一个一定不存在的数据会 被这个 bitmap 拦截掉,从而避免了对底层存储系统的查询压力。 5、Redis自带的bitmap 缓存雪崩:是指缓存同一时间大面积的失效,所以,后面的请求都会落到数据库上,造成数据库短时间 内承受大量请求而崩掉。

解决方案:

缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。 给每一个缓存数据增加相应的缓存标记,记录缓存是否失效,如果缓存标记失效,则更新数据缓 存。 搭建高可用Redis集群架构比如哨兵模式 业务服务层面使用降级、熔断、限流手段。(注意这是三套具体的解决方案落地)

15、什么情况下会出现数据库和缓存不一致的问题

数据库和缓存不一致的问题可能会出现在以下几种情况下:

  1. 写操作未更新缓存:当应用程序对数据库进行写操作时,如果没有及时更新相关的缓存数据,就会 导致数据库和缓存的数据不一致。这通常发生在缓存和数据库的更新操作没有保持同步的情况下。
  2. 缓存过期和数据库更新:当缓存中的数据过期时,如果此时有大量的并发请求查询该数据,而后端 数据库正在进行更新操作,就有可能导致缓存中的旧数据被读取,与数据库中的新数据不一致。
  3. 多级缓存不一致:在多级缓存架构中,不同层级的缓存可能会出现数据不一致的情况。例如,一级 缓存(本地缓存)和二级缓存(分布式缓存)之间的数据同步问题,如果没有及时更新或失效旧的 缓存数据,就会导致数据库和缓存数据的不一致。
  4. 数据库异常和缓存更新失败:当数据库发生异常或写操作失败时,如果缓存更新操作也失败了,就 会导致数据库和缓存的数据不一致。例如,数据库写操作成功了,但是缓存更新失败,导致缓存中 的数据是旧的或不一致的。

最终一句话:不管哪种方式,在高并发(读读读写)情况下都可能导致数据库数据和缓存数据不一致问

题。

16、如何解决Redis和数据库的一致性问题

解决方案一:双写模式

在写数据库的同时去将数据写入Redis 在无并发的情况下,重试即可。 在并发的情况下可能会数据不一致 慢的线程旧数据居然把新数据覆盖这是暂时性的脏数据问题,但是在数据稳定,缓存过期以后,又 能得到最新的正确数据。

双写模式下的:数据实时更新

当更新数据库的时候,同步更新缓存,并且加锁。 优点:数据一致性强 缺点:有耦合性,并且写和读的操作成为了串行,牺牲了高并发读数据的能力。 适用环境:适用于数据一致性要求高的场景,比如银行业务,证券交易业务。

双写模式下的:数据准实时更新

当更新数据库的同时,异步去更新缓存,比如更新数据库后把一条消息发送到mq中去。 优点:修改数据库数据的业务和修改缓存的数据业务完成了解耦。 缺点:有较短的延迟,并且可能无法保证最终一致性,需要补偿机制。【保证消息百分百投递】 适用环境:对数据实时性要求不严格的场景,比如对一些商品热度值的统计。

解决方案二:失效模式:

先删缓存在写数据库或者在写数据库的同时,去删除缓存, 在并发的情况下也可能出现不一致 会有两种情况: 情况一:先删除缓存,再更新数据库。 若异常情况:删除缓存成功了,更新数据库失败了。 无并发的情况下,重试修改数据库操作即可解决,保证最终的一致性。 有并发的情况下,给缓存数据设置过期时间,但是在缓存数据失效的这一段时间内,缓存数据仍然 和数据库数据是不一致的。因此还可以在写完数据库之后主动在来删除缓存。(双删)。思想: 【主动删除缓存+缓存key的被动失效】 情况二:先更新数据库值,再删除缓存值。 若异常情况:更新数据库成功了,删除缓存失败了。 无并发的情况下,重试删除缓存操作即可解决,保证最终的一致性。 有并发(读)的情况下,可以不用管,因为等待缓存删除完成,下一次在来读的时候,发现缓存缺 失,就会将数据最新的数据读到并同步到缓存中。在等待删除缓存期间也会有一段时间不一致 有并发(读、写)的情况下,仍然会出现缓存数据和数据库数据不一致问题,因此还需要通过缓存 数据的失效机制+主动删缓存的机制来保证缓存数据和数据库数据的一致性。(在删除缓存和缓存 数据失效期间仍然会出现不一致)

解决方案三:缓存失效机制

基于缓存本身的失效机制,具体实现方式为设置缓存失效时间,如果有缓存就从缓存中取数据,如果没 缓存就从数据库中取数据,并且重新设置缓存。 优点:实现方式简单,与业务完美解耦,不影响正常业务。 缺点:在缓存失效期间仍然会出现不一致,所以有数据一致性有延迟。 适用环境:能接受一定的数据延迟场景,比如对商品的热度值统计。

解决方案四:延时双删(主要是针对失效模式)

情况一:先删除缓存,再更新数据库。 在线程A更新完数据库值以后,我们可以让它先sleep一小段时间,再进行一次缓存删除操作。 之所以要加上sleep的这段时间,就是为了让线程B能够先从数据库读取数据,再把缺失的数据写入缓 存,然后,线程A再进行删除。所以,线程A sleep的时间,就需要大于线程B读取数据再写入缓存的时 间。这个时间怎么确定呢?建议你在业务程序运行的时候,统计下线程读数据和写缓存的操作时间,以 此为基础来进行估算。 Redis.delKey(X) db.update(X) Thread.sleep(N) Redis.delKey(X) 情况二:先更新数据库值,再删除缓存值。 如果线程A删除了数据库中的值,但还没来得及删除缓存值,线程B就开始读取数据了,那么此时,线程 B查询缓存时,发现缓存命中,就会直接从缓存中读取旧值。不过,在这种情况下,如果其他线程并发 读缓存的请求不多,那么,就不会有很多请求读取到旧值。而且,线程A一般也会很快删除缓存值,这 样一来,其他线程再次读取时,就会发生缓存缺失,进而从数据库中读取最新值。所以,这种情况对业 务的影响较小 db.update(X) Redis.delKey(X) Thread.sleep(N) Redis.delKey(X) 解决方案五:引入Canal中间件,类似于MySQL的主从数据同步。 大致的流程如下: 1)读Redis:热数据基本都在Redis 2)写MySQL:增删改都是操作MySQL 3)更新Redis数据:MySQ的数据操作binlog,来更新到Redis 第一次将数据全部写入到Redis中做一个全量备份,接这mysql中的insert update delet 作为增量进行实时更新。当读取到binglog后分析,利用消息对列,推送到各个Redis实例中去,对Redis 进行更新。 优点:类似于MySQL的主从模式,因为MySQL的主从也是通过订阅binlog日志来实现的数据一致性。 缺点:引入中间件,代码开发难度较大,成本较高。

17、Redis如何实现延迟消息

Redis本身并不提供延迟消息的特性,但可以通过一些技术手段实现延迟消息的功能。以下是一种基于 Redis的延迟消息实现方法:

  1. 使用有序集合(Sorted Set):将延迟消息的到期时间作为有序集合的分数(score),消息内容作为 有序集合的成员(member)。
  2. 将延迟消息添加到有序集合中:将延迟消息按照到期时间添加到有序集合中。
  3. 定时检查有序集合:通过定时任务或者后台线程,定期检查有序集合中的消息,找到到期的消息。
  4. 处理到期的消息:当有序集合中的消息到期时,将其从有序集合中移除,并进行相应的处理,可以 将消息发送到消息队列或者进行其他业务操作。 通过以上方法,可以实现延迟消息的功能。需要注意的是,在实现过程中需要考虑以下几点: 需要保证定时检查的频率,以确保消息能够及时被处理。 可以使用多个有序集合来支持不同延迟时间的消息,将消息按照不同的延迟时间分组存储。 可以使用Redis的发布/订阅功能将到期的消息发送到其他服务进行处理。 需要考虑消息的可靠性,如处理失败时的重试机制等。 需要根据具体的业务需求和系统架构选择合适的延迟消息方案,以上方法只是其中一种常见的实现方式 之一。

18、除了做缓存,Redis还能用来干什么

除了做缓存,Redis还可以用来实现以下几个功能:

  1. 数据存储:Redis支持多种数据结构,如字符串、哈希、列表、集合、有序集合等。可以将Redis作 为主要的数据存储,用于存储和查询数据。例如,可以将用户会话信息、配置信息、计数器等数据 存储在Redis中。
  2. 消息队列:Redis的发布/订阅功能可以用作简单的消息队列系统。发布者将消息发布到指定的频 道,订阅者可以监听频道并接收消息。这种方式可以用于实现异步任务、事件驱动等场景。
  3. 分布式锁:利用Redis的原子性操作和过期时间特性,可以实现分布式锁。通过在Redis中存储一个 特定键值对作为锁标识可以实现对共享资源的互斥访问,避免并发问题。
  4. 计数器:Redis的自增和自减操作可以用于实现计数器功能。可以用于统计网站的访问量、点赞数 量、订单数量等场景。
  5. 地理位置应用:Redis地理位置数据结构(Geo)可以存储和查询地理位置信息,如地理坐标、半 径查询等。可以用于实现附近的人、地理位置搜索等功能。
  6. 实时排行榜:利用Redis的有序集合数据结构,可以实现实时的排行榜功能。可以根据特定的规则 将成员和分数存储在有序集合中,并根据分数进行排名。
  7. 分布式缓存:Redis可以作为分布式缓存系统,通过集群和主从复制等机制,提供高可用性和高性 能的缓存服务。 总之,Redis不仅仅是一个缓存系统还可以用于实现多种功能和应用场景,包括数据存储、消息队列、分 布式锁、计数器、地理位置应用、实时排行榜等。

19、如何用SETNX实现分布式锁

使用Redis的SETNX命令可以实现简单的分布式锁。下面是使用SETNX实现分布式锁的基本步骤:

  1. 获取:当一个进程或线程需要获取锁时,使用SETNX命令尝试设置一个指定的键(作为锁的标 识)。
  2. 释放锁:当持有锁的进程或线程需要释放锁时,使用DEL命令删除对应的键。 通过以上步骤,可以实现简单的分布式锁。需要注意的是,分布式锁的实现需要考虑以下几点: 锁的粒度:需要明确锁的范围,即确定需要保护的共享资源。可以使用不同的锁标识来实现对不同 资源的锁定。 锁的超时:为了避免死锁,可以为获取到的锁设置一个超时时间,避免锁被长时间持有。 锁的可重入性:如果同一个进程或线程多次获取同一个锁,需要确保锁的可重入性,即能够正常释 放锁。 锁的可靠性:需要考虑锁的可靠性,如处理锁的异常情况、网络分区等。 锁的误删:B线程加入的锁,可能被A线程删除 锁的原子性:加锁设置过期时间和判断删除锁 需要根据具体的业务场景和系统需求,综合考虑以上因素来设计和实现分布式锁。

20、什么是RedLock,他解决了什么问题

SETNX lock_key 1

如果返回值为1,表示成功获取锁;如果返回值为0,表示锁已被其他进程或线程持有,获取锁失败。

DEL lock_key

RedLock是一个用于解决分布式系统中的锁竞争问题的算法。它是由Redis作者Antirez提出的一种算 法,旨在解决Redis的分布式锁在网络分区等异常情况下可能出现的问题。 在分布式系统中使用Redis的SETNX命令来实现锁时,可能会遇到网络分区(例如主节点与从节点之间的 网络断开)等故障情况,导致锁的可靠性受到影响。RedLock算法通过引入多个独立的Redis实例,使得 锁在多个Redis实例上创建,增加了锁的可靠性。 RedLock算法的基本思想是,使用多个Redis实例(理论上最少需要3个以上)来创建锁。在获取锁时, 需要在多个Redis实例上尝试获取锁,并使用大部分Redis实例都获取到锁才算成功。这样即使其中一个 实例出现故障或网络分区,仍然可以保证锁的可用性。 RedLock算法的步骤如下:

  1. 获取当前时间戳timestamp及随机字符串nonce。
  2. 在多个Redis实例上依次尝试获取锁,使用SET命令设置锁标识,设置过期时间为锁的超时时间(一 般为较短的时间,避免长时间锁定)。
  3. 统计成功获取到锁的Redis实例数。
  4. 如果成功获取到锁的Redis实例数大于等于大部分实例数(例如大于等于N/2+1,N为总实例数), 则认为获取锁成功。
  5. 如果获取锁失败,则需要在已获取锁的Redis实例上释放锁。 RedLock算法并不是用所有场景的通用解决方案,仍然存在一些局限性,例如对于时钟不同步的Redis实 例、网络延迟等情况可能会导致锁的可靠性下降。因此,在使用RedLock算法时需要根据具体的业务需 求和系统环境进行评估和测试。

21、如何用Redisson实现分布式锁

Redisson是一个基于Redis的分布式对象和服务的框架,它提供了一种简单且可靠的方式来实现分布式 锁。下面是使用Redisson实现分布式锁的基本步骤:

  1. 引入Redisson依赖:在项目的构建文件中引入Redisson的依赖,例如Maven的pom.xml文件中添 加以下依赖:
  2. 创建Redisson客户端:实例化Redisson客户端,并配置连接信息,例如Redis的地址和密码等。
  3. 获取分布式锁:通过Redisson的getLock方法获取一个分布式锁对象。
  4. 加锁和解锁:使用lock方法加锁,并在锁定的代码块执行完毕后使用unlock方法解锁。
<dependency>
<groupId>org.Redisson</groupId>
<artifactId>Redisson</artifactId>
<version>3.15.5</version>
</dependency>
Configconfig=newConfig();
config.useSingleServer().setAddress("Redis://127.0.0.1:6379").setPassword("your_
password");
RedissonClientRedisson=Redisson.create(config);
RLocklock=Redisson.getLock("myLock");

通过以上步骤,就可以使用Redisson实现分布式锁。Redisson还提供了一些其他的功能,如可重入锁、 公平锁、读写锁等,可以根据具体的需求选择合适的锁类型。此外,Redisson还支持异步执行和监听锁 状态的功能,提供了更多灵活和便捷的方法来操作分布式锁。 需要注意的是,在Redisson实现分布式锁时,仍然需要考虑锁的超时时间、可重入性、互斥性和可靠性 等因素,以确保分布式锁的正确和性能优化。

22、实现分布式锁,Zookeeper 和Redis哪种更好

为什么使用分布式锁? 使用分布式锁的目的,是为了保证同一时间只有一个 JVM 进程可以对共享资源进行操作。 根据锁的用途可以细分为以下两类: l 允许多个客户端操作共享资源,我们称为共享锁 这种锁的一般是对共享资源具有幂等性操作的场景,主要是为了避免重复操作共享资源频繁加锁带来的 性能开销。 l 只允许一个客户端操作共享资源,我们称为排他锁 这种锁一般是用在对共享资源操作具有非幂等性操作的场景,也就是需要保证在同一时刻只有一个进程 或者线程能够访问这个共享资源。 目前实现分布式锁最常用的中间件是 Redis 和Zookeeper

第一种,Redis 可以通过两种方式来实现

  1. 利用Redis 提供的`SET key value NX PX milliseconds(打印在屏幕上)`指令,这个指令是 设置一个key-value,如果key 已经存在,则返回 0,否则返回 1,我们基于这个返回值来判断锁的 占用情况从而实现分布式锁。
lock.locktry {

// 执行需要加锁的代码块

} finally {
lock.unlock();
}
  1. 基于Redission 客户端来实现分布式锁,Redisson 提供了分布式锁的封装方法,我们只需要调用 api 中的lock()和unlock()方法。它帮我们封装锁实现的细节和复杂度 Redisson 所有指令都通过 lua 脚本执行并支持lua 脚本原子性执行 Redisson 中有一个watchdog 的概念,翻译过来就是看门狗,它会在你获取锁之后,每隔 10 秒帮 你把key 的超时时间设为 30s,就算一直持有锁也不会出现key 过期了。“看门狗”的逻辑保证了没有 死锁发生。

第二种,基于ZK 实现分布式锁的落地方案

Zookeeper 实现分布式锁的方法比较多,我们可以使用有序节点来实现,

两种方案都有各自的优缺点

对于Redis 的分布式锁而言,它有以下缺点: 它获取锁的方式简单粗暴,如果获取不到锁,会不断尝试获取锁,比较消耗性能。 Redis 是AP 模型,在集群模式中由于数据的一致性会导致锁出现问题,即便使用 Redlock 算法来 实现,在某些复杂场景下,也无法保证其实现 100%的可靠性。不过在实际开发中使用 Redis 实现 分布式锁还是比较常见,而且大部分情况下不会遇到极端复杂的场景,更重要的是 Redis 性能很 高,在高并发场景中比较合适。 对于zk 分布式锁而言: zookeeper 天生设计定位就是分布式协调,强一致性。锁的模型健壮、简单易用、适合做分布式 锁。 如果获取不到锁,只需要添加一个监听器就可以了,不用一直轮询,性能消耗较小。 如果要在两者之间做选择,就我个人而言的话,比较推崇ZK 实现的锁,因为对于分布式锁而言,它应该 符合CP 模型,但是Redis 是AP 模型,所以在这个点上,Zookeeper 会更加合适。

23、缓存预热

什么是缓存预热?为什么需要它

缓存预热就是在系统正式对外提供服务之前,提前将热点数据加载到缓存中。 避免缓存穿透:防止大量请求直接打到数据库 提升用户体验:确保用户从一开始就能获得快速响应 保护数据库:避免数据库在系统启动时承受过大压力

缓存预热有哪些常见的实现方式

主动预热策略: 启动时预热:在启动时,通过初始化方法或监听器,主动查询热点数据并写入缓存 定时预热:适合数据更新频繁场景。通过定时任务,定期刷新缓存中的热点数据 手动预热:通过管理后台的按钮或接口,可以在需要时手动触发预热操作 被动预热策略: 懒加载+异步刷新:当缓存未命中时,先发牛默认值或从数据库查询,同时异步写入缓存 访问频率统计:系统记录每个数据的访问频次,自动将高频访问的数据加载到缓存中(实现较为复 杂) 混合预热策略: 分层预热:核心数据启动时预热,次要数据懒加载 分批预热:避免一次性加载过多数据造成系统压力,可按优先级分批次进行 智能预热:结合历史访问数据和业务规则,预测哪些数据可能被访问,提前加载

在实际项目中应该如何选择和实施

实施要点: 数据选择要精准 预热速度要控制 预热时机要合适 监控要到位 技术选型建议: Spring Boot项目:使用@PostConstruct 注解,实现ApplicationRunner接口,利用Spring的事件 机制实现启动预热 分布式系统:使用消息队列协调预热任务,避免多个实例重复预热 缓存技术: Redis的pipeline可以批量写入,提高预热效率。本地缓存如Caffeine也支持预热功能

24、什么是 IO 的多路复用机制

IO 多路复用机制,核心思想是让单个线程去监视多个连接,一旦某个连接就绪,也就是触发了读/写事 件。就通知应用程序,去获取这个就绪的连接进行读写操作。也就是在应用程序里面可以使用单个线程 同时处理多个客户端连接,在对系统资源消耗较少的情况下提升服务端的链接处理数量。 在IO 多路复用机制的实现原理中,客户端请求到服务端后,此时客户端在传输数据过程中(如图),为 了避免Server 端在read 客户端数据过程中阻塞,服务端会把该请求注册到 Selector 复路器上,服务端 此时不需要等待,只需要启动一个线程,通过selector.select()阻塞轮询复路器上就绪的channel 即 可,也就是说,如果某个客户端连接数据传输完成,那么select()方法会返回就绪的channel,然后执 行相关的处理就可以了。 常见的IO 多路复用机制的实现方式有: select 、poll、epoll。这些都是Linux 系统提供的IO 复用机制 的实现,其中select 和poll 是基于轮询的方式去获取就绪连接。而epoll 是基于事件驱动的方式获取就绪 连接。从性能的角度来看,基于事件驱动的方式要优于轮询的方式。

五、MQ

1、RabbitMQ面试题

1、RabbitMQ 的核心组件有哪些

RabbitMQ的核心组件包括以下几部分,他们共同构成了 RabbitMQ 的基本架构:

  1. Broker:RabbitMQ服务器,负责接收和分发消息的应用。
  2. Virtual Host:虚拟主机,是RabbitMQ中的逻辑容器,用于隔离不同环境或不同应用程序的信息 流。每个虚拟主机都有自己的队列、交换机等设置,可以理解为一个独立的RabbitMQ服务。
  3. Connection 连接:管理和维护与RabbitMQ服务器的TCP连接,生产者、消费者通过这个连接和 Broker 建立物理网络连接。
  4. Channel通道:是在Connection 内创建的轻量级通信通道,用于进行消息的传输和交互。应用程 序通过Channel进行消息的发送和接收。通常一个 Connection 可以建立多个 Channel。
  5. Exchange交换机:交换机是消息的中转站,负责接收来自生产者的消息,并将其路由到一个或多 个队列中。RabbitMQ 提供了多种不同类型的交换机,每种类型的交换机都有不同的消息路由规 则。
  6. Queue队列:队列是消息的存储位置。每个队列都有一个唯一的名称。消息从交换机路由到队列, 然后等待消费者来获取和处理。
  7. Binding绑定关系: Binding 是 Exchange 和 Queue 之间的关联规则,定义了消息如何从交换机 路由到特定的队列。 此外,生产者和消费者也是RabbitMQ的核心组件,生产者负责发送消息到Exchange或者 Queue,消费 者负责从Queue中订阅和处理消息。 这些核心组件共同构建了 RabbitMQ 的消息传递系统,他们协同工作才能实现消息的可靠传递、路由和 业务处理等功能。

2、RabbitMQ 和 AMQP 是什么关系

RabbitMQ 和 AMQP 有着非常密切的关系,但是他们是属于完全不同的两个概念。 AMQP: AMQP 不是一个具体的消息中间件产品,而是一个协议规范。他是一个开放的消息产地 协议,是一种应用层的标准协议,为面向消息的中间件设计。AMQP 提供了一种统一的消息服务, 使得不同程序之间可以通过消息队列进行通信。 SpringBoot 框架默认就提供了对 AMQP 协议的支 持。 RabbitMQ:RabbitMQ则是一个开源的消息中间件,是一个具体的软件产品。RabbitMQ 使用 AMQP 协议来实现消息传递的标准,但其实他也支持其他消息传递协议,如 STOMP 和 MQTT。 RabbitMQ 基于 AMQP 协议定义的消息格式和交互流程,实现了消息在生产者、交换机、队列之 间的传递和处理。 总之,AMQP 本质上是一个开放的标准,他不光可以被 RabbitMQ 实现,也可以被其他产品实现。通过 这种标准的协议,实际上是可以在不同的消息中间件系统之间进行灵活的消息传递。只不过,目前具体 实现这种标准的产品目前并不多,RabbitMQ 则是最有影响力的一个产品。因此,RabbitMQ 成了 AMQP 协议事实上的代表。SpringBoot 框架默认提供的 AMQP 协议支持底层也是基于 RabbitMQ 产品 实现的。

3、RabbitMQ 如何实现消息的持久化

RabbitMQ 允许消息的持久化,以确保即使在 RabbitMQ 服务器重新启动后,消息也不会丢失。 RabbitMQ 可以通过以下方式实现消息的持久化:

  1. 消息持久化:在 RabbitMQ 中,只需要在发送消息时,将delivery_mode属性设置为 2,就可以将 消息标记为持久化。
  2. 队列持久化:在 RabbitMQ 中声明队列时,也可以将队列声明为持久化。RabbitMQ 中的队列分为 三种不同类型经典队列,仲裁队列和流式队列。其中,经典队列需要将durable属性设置为true。 而仲裁队列和流式队列默认必须持久化保存。
  3. 交换机持久化:与经典队列类似,RabbitMQ 也可以在声明交换机时,将交换机的 durable 属性设 置为true,这样就可以将交换机标记为持久化。 RabbitMQ 的持久化机制会对其性能产生影响。因此,需要根据具体的业务场景和需求来权衡是否需要 持久化以及需要哪种类型的持久化。

4、RabbitMQ 是如何实现死信队列的

死信队列是 RabbitMQ 提供的一种特殊序列,处理那些无法被正常消费的消息。有三种情况会产生死 信: 消息被消费者明确拒绝。 消息达到预设的过期时间仍没有消费者消费。 消息由于队列已经达到最大长度限制而被丢弃。 在 RabbitMQ 中,实现死信队列只需要给正常队列增加三个核心参数即可:

  1. dead-letter-exchange:指定当前队列对应的死信队列
  2. dead-letter-routing-key:指定消息转入死信队列时的路由键
  3. message-ttl:消息在队列中的过期时间。 接下来,就可以往正常队列中发送消息。如果消息满足了某些条件,就会成为死信,并被重新发送到对 应的死信队列中。而此时,RabbitMQ 会在消息的头部添加一些与死信相关的补充信息,例如时间、成 为死信的原因、原队列等。应用程序可以按需处理这些补充的信息。 最后,死信队列中的消息都是正常业务处理失败的消息,应用程序需要创建一个消费者来专门处理这些 被遗漏的消息。例如记录日志、发送警报等。这样才能保证业务数据的完整性。

5、RabbitMQ 支持哪些消息模式

RabbitMQ是一种流行的消息中间件,它支持多种工作模式,以满足不同的消息传递需求。以下是 RabbitMQ中常见的几种工作模式:

  1. 简单模式(Simple Queue): 也被称为点对点模式。 在这种模式下,有一个生产者(Producer)将消息发送到队列(Queue),然后有一个消费 者(Consumer)从队列中接收和处理消息。 应用场景:将发送的电子邮件放到消息队列,然后邮件服务在队列中获取邮件并发送给收件人 2.工作队列模式(Work queues) 在多个消费者之间分配任务(竞争的消费者模式),一个生产者对应多个消费者,一般适用于执行资源 密集型任务,单个消费者处理不过来,需要多个消费者进行处理。 应用场景:一个订单的处理需要10s,有多个订单可以同时放到消息队列,然后让多个消费者同时处 理,这样就是并行了,而不是单个消费者的串行情况。

3.发布订阅模式(Publish/Subscribe)

一次向许多消费者发送消息,一个生产者发送的消息会被多个消费者获取,也就是将消息将广播到所有 的消费者中。 应用场景:更新商品库存后需要通知多个缓存和多个数据库,这里的结构应该是: 一个fanout类型交换机扇出两个个消息队列,分别为缓存消息队列、数据库消息队列 一个缓存消息队列对应着多个缓存消费者 一个数据库消息队列对应着多个数据库消费者 4、路由模式(Routing) 有选择地(Routing key)接收消息,发送消息到交换机并且要指定路由key ,消费者将队列绑定到交换 机时需要指定路由key,仅消费指定路由key的消息 应用场景:如在商品库存中增加了1台iphone12,iphone12促销活动消费者指定routing key为 iphone12,只有此促销活动会接收到消息,其它促销活动不关心也不会消费此routing key的消息。 5、主题模式(Topics) 根据主题(Topics)来接收消息,将路由key和某模式进行匹配,此时队列需要绑定在一个模式上,#匹 配一个词或多个词,*只匹配一个词。 应用场景:同上,iphone促销活动可以接收主题为iphone的消息,如iphone12、iphone13等。 6、发布者确认(Publisher Confirms) 与发布者进行可靠的发布确认,发布者确认是RabbitMQ扩展,可以实现可靠的发布。在通道上启用发 布者确认后,RabbitMQ将异步确认发送者发布的消息。 应用场景:对于消息可靠性要求较高,比如钱包扣款。而一旦我们使用了消息队列,我们基本都要保证 消息的百分百投递,因此建议使用的时候尽量选择该模式。

7、RPC(这种模式用的很少)

6、RabbitMQ 中如何进行事务处理

通过对信道的设置实现

  1. channel.txSelect();通知服务器开启事务模式;服务端会返回Tx.Select-Ok
  2. channel.basicPublish;发送消息,可以是多条,可以是消费消息提交ack
  3. channel.txCommit()提交事务;
  4. channel.txRollback()回滚事务;

消费者使用事务:

  1. autoAck=false,手动提交ack,以事务提交或回滚为准;
  2. autoAck=true,不支持事务的,也就是说你即使在收到消息之后在回滚事务也是于事无补的,队列 已经把消息移除了 如果其中任意一个环节出现问题,就会抛出IoException异常,用户可以拦截异常进行事务回滚,或决定 要不要重复消息。事务消息会降低RabbitMQ的性能。

7、RabbitMQ 中有哪几种交换机类型

RabbitMQ 支持多种交换机(Exchange)类型,每种类型都用于不同的消息路由和分发策略:

  1. Direct Exchange: 这种交换机根据消息的路由键(Routing Key)将消息发送到与之完全匹配的队列。只有当消息的路由键 与队列绑定时指定的路由键完全相同时,消息才会被路由到队列。这是一种简单的路由策略,适用于点 对点通信。
  2. Topic Exchange: 这种交换机根据消息的路由键与队列绑定时指定的路由键模式(通配符)匹配程度,将消息路由到一个 或多个队列。路由键可以使用通配符符号*(匹配一个单词)和#(匹配零个或多个单词),允许更灵 活的消息路由。用于发布/订阅模式和复杂的消息路由需求。
  3. Headers Exchange: 这种交换机根据消息的标头信息(Headers)来决定消息的路由,而不是使用路由键。队列和交换机之 间的绑定规则是根据标头键值对来定义的,只有当消息的标头与绑定规则完全匹配时,消息才会被路由 到队列。适用于需要复杂消息匹配的场景。
  4. Fanout Exchange: 这种交换机将消息广播到与之绑定的所有队列,无论消息的路由键是什么。用于发布/订阅模式,其中一 个消息被广播给所有订阅者。
  5. Default Exchange: 这是 RabbitMQ 默认实现的一种交换机,它不需要手动创建。当消息发布到默认交换机时,路由键会被 解释为队列的名称,消息会被路由到与路由键名称相同的队列。默认交换机通常用于点对点通信,但不 支持复杂的路由策略。

8、RabbitMQ交换机类型

direct(直连交换机)

作为默认交换机。路由键与队列名完全匹配交换机,此种类型交换机,通过RoutingKey路由键将交换机 和队列进行绑定,消息被发送到exchange时,需要根据消息的RoutingKey,来进行匹配,只将消息发 送到完全匹配到此RoutingKey的队列。 比如:如果一个队列绑定到交换机要求路由键为“key”,则只转发RoutingKey标记为“key”的消息,不会 转发”key1”,也不会转发“key.1”等等。它是完全匹配、单播的模式

<font style="color:rgb(18, 18, 18);">同一个key可以绑定多个queue队列;当匹配到key1时,
queue1和queue2都可以收到消息</font>

fanout(扇型交换机,广播)

Fanout,扇出类型交换机,此种交换机,会将消息分发给所有绑定了此交换机的队列,此时RoutingKey 参数无效。 fanout类型交换机下发送消息一条,无论RoutingKey是什么,直接广播消息, queue1,queue2,queue3,queue4都可以收到消息

topic(主题交换机)

Topic,主题类型交换机,此种交换机与Direct类似,也是需要通过routingkey路由键进行匹配分发,区 别在于Topic可以进行模糊匹配,Direct是完全匹配。

  1. Topic中,将routingkey通过”.”来分为多个部分
  2. ”*“:代表一个部分
  3. ”#“:代表0个或多个部分(如果绑定的路由键为 ”#” 时,则接受所有消息,因为路由键所有都匹配) 然后发送一条信息,routingkey为”key1.key2.key3.key4”,那么根据”.”将这个路由键分为了4个部分, 此条路由键,将会匹配:
  4. key1.key2.key3.*:成功匹配,因为 * 可以代表一个部分
  5. key1.# :成功匹配,因为#可以代表0或多个部分
  6. .key2..key4:成功匹配,因为第一和第三部分分别为key1和key3,且为4个部分,刚好匹配
  7. #.key3.key4:成功匹配,#可以代表多个部分,正好匹配中了我们的key1和key2 如果发送消息routingkey为”key1”,那么将只能匹配中key1.#,#可以代表0个部分

headers(头部交换机)

headers 匹配 AMQP 消息的 header 而不是路由键,此外 headers 交换器和 direct 交换器完全一致, 但性能差很多,目前几乎用不到了 消费方指定的headers中必须包含一个”x-match”的键。 键”x-match”的值有2个

  1. x-match = all :表示所有的键值对都匹配才能接受到消息
  2. x-match = any :表示只要有键值对匹配就能接受到消息 发送消息时间,如果其他参数信息是{ “name”:“TianMingXX”, “sex”:“男” },因为queue2的x-match是 any,只需要有一个键值对匹配所以就能接收到消息,所以queue2可以接收到消息;queue1的x-match 是all,需要所有的键值对都匹配才能接收到消息,所以此时queue1接收不到消息

9、RabbitMQ架构设计

RabbitMQ 是一个开源的消息中间件,采用 AMQP(高级消息队列协议)进行消息传递。它允许应用程 序之间进行异步通信,提供了一种高效、可扩展、可靠的消息传递机制。 以下是 RabbitMQ 的基本架构设计:

  1. 生产者(Producer):生产者是消息的发送方,负责产生并发送消息到 RabbitMQ。生产者通常 将消息发送到交换机(Exchange)。
  2. 交换机(Exchange):交换机是消息的分发中心,负责将接收到的消息路由到一个或多个队列。 它定义了消息的传递规则,可以根据规则将消息发送到一个或多个队列。 直连交换机(Direct Exchange):将消息路由到与消息中的路由键(Routing Key)完全匹 配的队列。 主题交换机(Topic Exchange):根据通配符匹配路由键,将消息路由到一个或多个队列。 扇出交换机(Fanout Exchange):将消息广播到所有与交换机绑定的队列,忽略路由键。 头部交换机(Headers Exchange):根据消息头中的属性进行匹配,将消息路由到与消息 头匹配的队列。
  3. 队列(Queue):队列是消息的存储区,用于存储生产者发送的消息。消息最终会被消费者从队 列中取出并处理。每个队列都有一个名称,并且可以绑定到一个或多个交换机。
  4. 消费者(Consumer):消费者是消息的接收方,负责从队列中获取消息并进行处理。消费者通 过订阅队列来接收消息。
  5. 绑定(Binding):绑定是交换机和队列之间的关联关系。生产者将消息发送到交换机,而队列通 过绑定与交换机关联,从而接收到消息。
  6. 虚拟主机(Virtual Host):虚拟主机是 RabbitMQ 的基本工作单元,每个虚拟主机拥有自己独 立的用户、权限、交换机、队列等资源,完全隔离于其他虚拟主机。
  7. 连接(Connection):连接是指生产者、消费者与 RabbitMQ 之间的网络连接。每个连接可以包 含多个信道(Channel),每个信道是一个独立的会话通道,可以进行独立的消息传递。
  8. 消息:消息是生产者和消费者之间传递的数据单元。消息通常包含消息体和可选的属性,如路由键 等。

10、RabbitMQ如何保证消息不丢失

丢失原因分析

观察整个 RabbitMQ 消息发送过程: 从上述流程我们可以得知:消息从生产者到达消费者,经过两次网络传输,并且在 RabbitMQ 服务器中 进行路由。 因此我们能知道整个流程中可能会出现三种消息丢失场景: 生产者发送消息到 RabbitMQ 服务器的过程中出现消息丢失。可能是网络波动未收到消息,又或 者是服务器宕机。 RabbitMQ 服务器消息持久化出现消息丢失。消息发送到 RabbitMQ 之后,未能及时存储完成持 久化,RabbitMQ 服务器出现宕机重启,消息出现丢失。 消费者拉取消息过程以及拿到消息后出现消息丢失。消费者从 RabbitMQ 服务器获取到消息过程 出现网络波动等问题可能出现消息丢失;消费者拿到消息后但是消费者未能正常消费,导致丢失, 可能是消费者出现处理异常又或者是消费者宕机。 针对上述三种消息丢失场景,RabbitMQ 提供了相应的解决方案,confirm 消息确认机制(生产者), 消息持久化机制(RabbitMQ 服务),ACK 事务机制(消费者)

解决方案

confirm 消息确认机制(生产者)

Confirm 模式是 RabbitMQ 提供的一种消息可靠性保障机制。当生产者通过 Confirm 模式发送消息时, 它会等待 RabbitMQ 的确认,确保消息已经被正确地投递到了指定的 Exchange 中。 消息正确投递到 queue 时,会返回 ack。 消息没有正确投递到 queue 时,会返回 nack。如果 exchange 没有绑定 queue,也会出现消息丢失。

使用方法:

生产者通过confirm.select方法将 Channel 设置为 Confirm 模式。 发送消息后,通过添加add_confirm_listener方法,监听消息的确认状态。 附源码: 1.开启消息确认机制

spring:
RabbitMQ:
#开启消息确认机制
publisher-confirms: true

#消息在未被队列收到的情况下返回

#publisher-returns: true
#publisher-confirm-type: correlated
2.消息未接收时调用ReturnCallback
rabbitTemplate.setMandatory(true);

3.生产者投递消息

@Service
publicclassConfirmProviderimplements
RabbitTemplate.ConfirmCallback,RabbitTemplate.ReturnCallback {
@Autowired
RabbitTemplaterabbitTemplate;
@PostConstruct
publicvoidinit() {
rabbitTemplate.setReturnCallback(this);
rabbitTemplate.setConfirmCallback(this);
}
@Override
publicvoidconfirm(CorrelationDatacorrelationData, booleanack, String
cause) {
if(ack){
System.out.println("确认了这条消息:"+correlationData);
}else{
System.out.println("确认失败了:"+correlationData+";出现异常:"+cause);
}
}
@Override
publicvoidreturnedMessage(Messagemessage, intreplyCode, String
replyText, Stringexchange, StringroutingKey) {
System.out.println("这条消息发送失败了"+message+",请处理");
}
publicvoidpublisMessage(Stringmessage){
rabbitTemplate.setMandatory(true);

消息持久化机制(RabbitMQ 服务)

持久化机制是指将消息存储到磁盘,以保证在 RabbitMQ 服务器宕机或重启时,消息不会丢失。

使用方法:

生产者通过将消息的delivery_mode属性设置为 2(SpringBoot中deliveryMode默认为 MessageDeliveryMode.PERSISTENT 等于2),将消息标记为持久化。 队列也需要进行持久化设置,确保队列在 RabbitMQ 服务器重启后仍然存在。需要将durable属性 设置为true,且需配合autoDelete设置为false

注意事项:

持久化机制会影响性能,因此在需要确保消息不丢失的场景下使用。 附源码:

ACK 事务机制(消费者)

ACK 事务机制用于确保消息被正确消费。当消息被消费者成功处理后,消费者发送确认(ACK)给 RabbitMQ,告知消息可以被移除。这个过程是自动处理的,也可以关闭进行手工发送 ACK。

使用方法:

在 RabbitMQ 中,ACK 机制默认是开启的。当消息被消费者接收后,会立即从队列中删除,除非 消费者发生异常。 可以手动开启 ACK 机制,通过将auto_ack参数设置为False,手动控制消息的 ACK (acknowledge-mode: manual)。

注意事项:

ACK 机制可以确保消息不会被重复处理,但如果消费者发生异常或者未发送 ACK,消息可能会被重 复投递。

rabbitTemplate.convertAndSend("javatrip",message);
}
}

4.如果消息确认失败后,我们可以进行消息补偿,也就是消息的重试机制。当未收到确认信息时进行消息的重 新投递。设置如下配置即可完成。

spring:
RabbitMQ:

#支持消息发送失败后重返队列

publisher-returns: true

#开启消息确认机制

#publisher-confirm-type: correlated
listener:
simple:
retry:
#开启重试
enabled: true
#最大重试次数
max-attempts: 5
#重试时间间隔
@Queue(value="YumaQ",durable="true",autoDelete="false")   //或者注入时
returnnewQueue("YumaQ", true, false, false, map);

附源码: 当然上面确保不丢失,会自动重试,可能会造成重复消费问题。具体处理可往下看第5点。

11、RabbitMQ中如何解决消息堆积问题

消息堆积原因 1.修改yml为手动签收模式

spring:
RabbitMQ:
listener:
simple:
#手动签收模式
acknowledge-mode: manual
#每次签收一条消息
prefetch: 1

2.消费者手动签收

@Component
@RabbitListener(queuesToDeclare=@Queue(value="YumaQ", durable="true"))
publicclassSecondConsumer {
@RabbitHandler
publicvoidreceive(Stringmessage, @HeadersMap<String,Object>headers,
Channelchannel) throwsException{
System.out.println(message);
// 唯一的消息ID
LongdeliverTag= (Long) headers.get(AmqpHeaders.DELIVERY_TAG);
// 确认该条消息
if(...){
channel.basicAck(deliverTag,false);
}else{
// 消费失败,消息重返队列
channel.basicNack(deliverTag,false,true);
}
}

解决方案

  1. 消费者处理消息的速度太慢 增加消费者数量:通过水平扩展,增加消费者的数量来提高处理能力。 优化消费者性能:提高消费者处理消息的效率,例如优化代码、增加资源。 消息预取限制(prefetch count):调整消费者的预取数量以避免一次处理过多消息而导致处 理缓慢。
  2. 队列的容量太小 增加队列的容量:调整队列设置以允许更多消息存储。
  3. 网络故障 监控和告警:通过监控网络状况并设置告警,确保在网络故障时快速发现并解决问题。 持久化和高可用性:确保消息和队列的持久化以避免消息丢失,并使用镜像队列提高可用性。
  4. 消费者故障 使用死信队列:将无法处理的消息转移到死信队列,防止堵塞主队列。 容错机制:实现消费者的自动重启和错误处理逻辑。
  5. 队列配置不当 优化队列配置:检查并优化消息确认模式、队列长度限制和其他相关配置。
  6. 消息大小 消息分片:将大型消息分割成小的消息片段,加快处理速度。
  7. 业务逻辑复杂或耗时 优化业务逻辑:简化消费者中的业务逻辑,减少处理每个消息所需的时间。
  8. 消息产生速度快于消费速度 使用消息限流:控制消息的生产速度,确保它不会超过消费者的处理能力。 负载均衡:确保消息在消费者之间公平分配,避免个别消费者过载。
  9. 其他配置优化 消息优先级:使用消息优先级确保高优先级消息优先处理。 调整RabbitMQ配置:优化RabbitMQ服务的配置,如文件描述符限制、内存使用限制等。

12、RabbitMQ中如何保证消息不被重复消费

什么情况会导致消息被重复消费呢

  1. 生产者:生产者可能会重复推送一条数据到 MQ 中,比如 Controller 接口被重复调用了 2 次,没 有做接口幂等性导致的;
  2. MQ:在消费者消费完准备响应 ack 消息消费成功时,MQ 突然挂了,导致 MQ 以为消费者还未消 费该条数据,MQ 恢复后再次推送了该条消息,导致了重复消费。
  3. 消费者:消费者已经消费完消息,正准备但是还未响应给ack消息到时,此时消费者挂了,服务重 启后 MQ 以为消费者还没有消费该消息,再次推送了该条消息。

解决方案

使用数据库唯一键约束

缺点:局限性很大,仅仅只能用在我们数据新增场景,并且性能也比较低

使用乐观锁

假设是更新订单状态,在发送的消息的时候带上修改字段的版本号 缺点:如果说更新字段比较多,并且更新场景比较多,可能会导致数据库字段增加并且还有可能出现多 条消息同时在队列中此时他们修改字段版本号一致,排在后续的消息无法被消费

简单的消息去重,插入消费记录,增加数据库判断

优点:很多场景下的确能起到不错的效果 缺点:

  1. 这个消费者的代码执行需要1秒,重复消息在执行期间(假设100毫秒)内到达(例如生产者快速重 发,Broker重启等),增加校验的地方是不是还是没数据(因为上一条消息还没消费完,没有记 录)
  2. 那么就会穿透掉检查的挡板,最后导致重复的消息消费逻辑进入到非幂等安全的业务代码中,从而 引发重复消费的问题

并发消息去重基于消息幂等表

缺点:如果说第一次消息投递异常没有消费成功,并且没有将消息状态给置为成功或者没有删除消 息表记录,此时延时消费每次执行下列都是一直处于消费中,最后消费就会被视为消费失败而被投 递到死信Topic中 方案:插入的消息表必须要带一个最长消费过期时间,例如10分钟 上述方案只需要一个存储的中心媒介,那我们可以选择更灵活的存储中心媒介,比如Redis。使用 Redis有两个好处: 性能上损耗更低 上面我们讲到的超时时间可以直接利用Redis本身的ttl实现

总结

  1. 利用数据库唯一键约束
  2. 可以利用我们的乐观锁
  3. 插入消费记录 不丢和不重是矛盾的(在分布式场景下),总的来说,开发者根据业务的实际需求来选择相应的方式即 可。

13、RabbitMQ如何实现延迟消息

RabbitMQ本身并不直接支持延迟消息的功能,但可以通过结合使用RabbitMQ的一些特性来实现延迟消 息的效果。下面介绍两种常见的实现方式:

  1. 利用消息的过期时间和死信队列(DLX):这种方式可以通过设置消息的过期时间来实现延迟消息 的效果。具体步骤如下: 创建一个普通的交换机和队列用于接收延迟消息。 设置队列的消息过期时间,可以通过设置队列的x-message-ttl 参数或通过单独设置消息的
expiration 属性。

设置队列的死信交换机和死信路键,将过期的消息发送到指定的死信交换机和路由键。 创建一个死信交换机和队列,用于处理过期的消息。 将队列绑定到死信交换机上,指定合适的路由键。 发送延迟消息时,将消息发送到普通的交换机和队列。 消息会在指定的过期时间后发送到死信交换机和队列,从而实现延迟消息的效果。 2. 使用RabbitMQ的延迟插件(RabbitMQ_delayed_message_exchange):RabbitMQ社区提供了 一个延迟插件,可以直接实现延迟消息的功能。具体步骤如下: 下载并安装RabbitMQ_delayed_message_exchange插件。 启用延迟插件,通过RabbitMQ的管理界面或命令行工具进行配置。 创建一个延迟交换机和队列,将延迟插件应用到交换机上。 发送延迟消息时,将消息发送到延迟交换机和队列,同时设置消息的延迟时间。 延迟交换机会根据消息的延迟时间将消息发送到指定的目标队列,从而实现延迟消息的效果。 需要注意的是,以上两种方式都是通过消息的过期时间来实现延迟消息的,因此在使用时需要根据实际 需求和性能考虑合适的延迟时间设置。另外,延迟消息的实现可能会增加系统的复杂性和消息的处理延 迟,需要根据具体业务场景进行评估和选择。

14、RabbitMQ如何保证消息的顺序

消息队列中的若干消息如果是对同一个数据进行操作,这些操作具有前后的关系,必须要按前后的顺序 执行,否则就会造成数据异常。举例: 比如通过mysql binlog进行两个数据库的数据同步,由于对数据库的数据操作是具有顺序性的,如果操 作顺序搞反,就会造成不可估量的错误。比如数据库对一条数据依次进行了插入->更新->删除操作,这 个顺序必须是这样,如果在同步过程中,消息的顺序变成了删除->插入->更新,那么原本应该被删除的 数据,就没有被删除,造成数据的不一致问题。 举例场景: RabbitMQ: ①一个queue,有多个consumer去消费,这样就会造成顺序的错误,consumer从MQ里面读取数据是 有序的,但是每个consumer的执行时间是不固定的,无法保证先读到消息的consumer一定先完成操 作,这样就会出现消息并没有按照顺序执行,造成数据顺序错误。 ②一个queue对应一个consumer,但是consumer里面进行了多线程消费,这样也会造成消息消费顺序 错误。 解决方案: ①拆分多个queue,每个queue一个consumer,就是多一些queue而已,确实是麻烦点;这样也会造成 吞吐量下降,可以在消费者内部采用多线程的方式取消费。 一个queue对应一个consumer ②或者就一个queue但是对应一个consumer,然后这个consumer内部用内存队列做排队,然后分发给 底层不同的worker来处理 一个queue对应一个consumer,采用多线程

15、如何使用RabbitMQ解决分布式事务

分布式事务:不同的服务操作不同的数据源(库或表),保证数据一致性的问题。 解决:采用RabbitMQ消息最终一致性的解决方案,解决分布式事务问题。 分布式事务场景: 1、电商项目中的商品库和ES库数据同步问题。 2、电商项目中:支付----订单---库存,一系列操作,进行状态更改等。 在互联网应用中,基本都会有用户注册的功能。在注册的同时,我们会做出如下操作: 收集用户录入信息,保存到数据库向用户的手机或邮箱发送验证码等等… 如果是传统的集中式架构,实现这个功能非常简单:开启一个本地事务,往本地数据库中插入一条用户 数据,发送验证码,提交事物。 但是在分布式架构中,用户和发送验证码是两个独立的服务,它们都有各自的数据库,那么就不能通过 本地事物保证操作的原子性。这时我们就需要用到 RabbitMQ(消息队列)来为我们实现这个需求。 在用户进行注册操作的时候,我们为该操作创建一条消息,当用户信息保存成功时,把这条消息发送到 消息队列。验证码系统会监听消息,一旦接受到消息,就会给该用户发送验证码。

16、如何解决消息队列的延时以及过期失效问题?消息队列满了之后

该如何处理?有几百万的消息持续积压几小时,说说如何解决

方案分析 该问题,其本质针对的场景,都是说,可能你的消费端出了问题,不消费了,或者消费的极其极其慢。另 外还有可能你的消息队列集群的磁盘都快写满了,都没人消费,这个时候怎么办?或者是整个这就积压 了几个小时,你这个时候怎么办?或者是你积压的时间太长了,导致比如RabbitMQ设置了消息过期时 间后就没了怎么办? 所以这种问题线上常见的,一般不出,一出就是大问题,一般常见于,举个例子,消费端每次消费之后 要写mysql,结果mysql挂了,消费端挂掉了。导致消费速度极其慢。 分析1+话术 这个是我们真实遇到过的一个场景,确实是线上故障了,这个时候要不然就是修复consumer的问题, 让他恢复消费速度,然后傻傻的等待几个小时消费完毕。(可行,但是不建议在面试的时候说) 一个消费者一秒是1000条,一秒3个消费者是3000条,一分钟是18万条,1000多万条 所以如果你积压了几百万到上千万的数据,即使消费者恢复了,也需要大概1小时的时间才能恢复过来 一般这个时候,只能操作临时紧急扩容了,具体操作步骤和思路如下: 1)先修复consumer的问题,确保其恢复消费速度,然后将现有cnosumer都停掉 2)新建一个topic,partition是原来的10倍,临时建立好原先10倍或者20倍的queue数量 3)然后写一个临时的分发数据的consumer程序,这个程序部署上去消费积压的数据,消费之后不做耗 时的处理,直接均匀轮询写入临时建立好的10倍数量的queue 4)接着临时征用10倍的机器来部署consumer,每一批consumer消费一个临时queue的数据 5)这种做法相当于是临时将queue资源和consumer资源扩大10倍,以正常的10倍速度来消费数据 6)等快速消费完积压数据之后,得恢复原先部署架构,重新用原先的consumer机器来消费消息 分析2+话术 RabbitMQ是可以设置过期时间的,就是TTL,如果消息在queue中积压超过一定的时间就会被 RabbitMQ给清理掉,这个数据就没了。那这就是第二个坑了。这就不是说数据会大量积压在mq里,而 是大量的数据会直接搞丢。 这个情况下,就不是说要增加consumer消费积压的消息,因为实际上没啥积压,而是丢了大量的消 息。我们可以采取一个方案,就是批量重导,这个我们之前线上也有类似的场景干过。就是大量积压的 时候,我们当时就直接丢弃数据了,然后等过了高峰期以后,比如大家一起喝咖啡熬夜到晚上12点以 后,用户都睡觉了。 这个时候我们就开始写程序,将丢失的那批数据,写个临时程序,一点一点的查出来,然后重新灌入mq 里面去,把白天丢的数据给他补回来。也只能是这样了。 假设1万个订单积压在mq里面,没有处理,其中1000个订单都丢了,你只能手动写程序把那1000个订 单给查出来,手动发到mq里去再补一次 分析3+话术 如果走的方式是消息积压在mq里,那么如果你很长时间都没处理掉,此时导致mq都快写满了,咋办? 这个还有别的办法吗?没有,谁让你第一个方案执行的太慢了,你临时写程序,接入数据来消费,消费 一个丢弃一个,都不要了,快速消费掉所有的消息。然后走第二个方案,到了晚上再补数据吧。

17、为什么要使用 RabbitMQ

(1)在分布式系统下具备异步,削峰,负载均衡等一系列高级功能; (2)拥有持久化的机制,进程消息,队列中的信息也可以保存下来。 (3)实现消费者和生产者之间的解耦。 (4)对于高并发场景下,利用消息队列可以使得同步访问变为串行访问达到一定量的限流,利于数据库 的操作。 (5)可以使用消息队列达到异步下单的效果,排队中,后台进行逻辑下单。

18、使用 RabbitMQ 的场景

(1)服务间异步通信 (2)顺序消费 (3)定时任务 (4)请求削峰

19、消息基于什么传输

由于 TCP 连接的创建和销毁开销较大,且并发数受系统资源限制,会造成性能瓶颈。它信道的方式来传 输数据。信道是建立在真实的 TCP 连接内的虚拟连接,且每条 TCP 连接上的信道数量没有限制。

20、消息如何分发

若该队列至少有一个消费者订阅,消息将以循环(round-robin)的方式发送给消费者。每条消息只会 分发给一个订阅的消费者(前提是消费者能够正常处理消息并进行确认)。通过路由可实现多消费的功 能

21、RabbitMQ 的集群

普通集群 (只同步结构,不同步消息) 镜像集群 (都同步,出现脑裂) 仲裁队列 (都同步,没有脑裂) [推荐] Stream流 (不成熟,效率不如kafka) 第三方实现 Shovel Pluging (适用于跨集群或不同管理域的节点间传递消息,支持不同用户、vhost 和 RabbitMQ 版本) 适用于需要跨集群传递消息且对消息可靠性要求不高的场景(如日志同步) Federation Plugins (同样支持跨集群消息传递,但更强调集群间的松耦合,允许节点独立运 行) 适用于需要集群间松耦合通信的场景(如分布式系统中的服务调用)。

2、Kafka面试题

1、Kafka都有哪些特点

高吞吐量、低延迟:kafka每秒可以处理几十万条消息,它的延迟最低只有几毫秒,每个topic可以分多 个partition, consumer group 对partition进行consume操作。 •可扩展性: •持久性、可靠性:消息被 持久化到本地磁盘,并且支持数据备份防止数据丢失 •容错性:允许集群中节点失败 •高并发:支持数千 个客户端同时读写

2、Kafka的选择场景

日志收集:一个公司可以用Kafka可以收集各种服务的log,通过kafka以统一接口服务的方式开放给各种 consumer,例如hadoop、HBase等。 消息系统:解耦和上游生产者和下游消费者、缓存消息等。 用户活动跟踪:Kafka经常被用来记录web用户或者app用户的各种活动,如浏览网页、搜索、点击等活 动,这些活动信息被各个服务器发布到kafka的topic中,然后订阅者通过订阅这些topic来做实时的监控 分析。 运营指标:Kafka也经常用来记录运营监控数据。包括收集各种分布式应用的数据,生产各种操作的集中 反馈,比如报警和报告。

3、Kafka的架构设计

从高阶和顶层看,生产者发送消息到kafka集群,然后消费者来获取消息进行后续处理

核心组件

概念

Producer

生产者,可以向Broker topic发布消息的客户端

Consumer

消费者,从Broker topic订阅取消息的客户端

Broker

Broker是一个kafka实例,简单说就是一台kafka服务器,

kafkaCluster表示集群。一个Linux服务器上启动一个或多个 kafka

服务器实例。

Topic

主题,Kafka将消息分门别类,每一类的消息称之为一个主题。可以理

解为一个队列,生产者和消费者面向的都是一个topic

Partition

Topic的分区,每个 Topic 可以有多个分区,同一个Topic 在不同分区

的数据是不重复的,每个Partition是一个有序的队列。分区作用是做

负载,提高 kafka 的吞吐量以及提高读写并行能力。每个partition都

由一系列有序的、不可变的消息组成,这些消息被连续的追加到

partition中。partition中的每个消息都有一个连续递增的序列号叫做

offset,偏移量offset在每个分区中是唯一的。

Offset

生产者Offset:消息写入的时候,每一个分区都有一个offset,这个

offset就是生产者的offset,同时也是这个分区的最新最大的offset。

消费者Offset:某个分区的offset情况。例如:生产者写入的offset是

最新值是10,当一个Consumer开始消费时,从0消费,一直消费到了

5,消费者的offset为5。

Replication

Partition(分区)的副本。每个分区可以有多个Replication,由一个

Leader和若干个Follower组成。Leader负责接收生产者push的消息

和消费者poll消费消息。Follower会实时从自己的Leader中同步数据

保持同步。Leader故障时,某个Follower会上位为新的Leader。保证

高可用。

Message

kafka集群存储的消息是以topic为类别记录的,每个消息(也叫记录

record)

ConsumerGroup(CG)

消费者组,由多个Consumer组成,每个ConsumerGroup中可以有

多个consumer,每个consumer属于一个ConsumerGroup。同一个

Topic下的某一个分区只能被某个消费者组内的同一个消费者所消费,

但可以被多个 consumer group 消费

In-sync Replicas

(ISR)

(ISR)已同步副本:表示存活且副本都已和Leader同步的的broker

集合,是Leader所有replicas副本的子集(包括leader本身)。如果

某个副本节点宕机,该副本就会从ISR集合中剔除。

4、Kafka 分区的目的

容

错

性

由于数据被分散存储到多个Partition上,即使某个节点或者Partition出现故障,也不会影

响整个Topic数据的完整性和可用性。

水 平 扩 展 性 通过将Topic划分为多个Partition,Kafka可以实现水平扩展,从而提高整体的吞吐量。这 意味着它可以同时处理更多的消息和请求,而不会因为单个节点或磁盘容量的限制而导致性 能瓶颈。 并 行 处 理 每个Partition可以由不同的消费者并行处理,提高了系统的处理能力。 顺 序 性 在同一个Partition内部,消息是按照一定的顺序存储的。这对于那些需要在特定顺序下处理 消息的场景来说是一个重要的特性,因为它保证了消息的有序性。

5、ISR、AR 是什么

ISR:In-Sync Replicas 副本同步队列 AR:Assigned Replicas 所有副本

6、LEO、HW、LW等分别代表什么

LEO:是 LogEndOffset 的简称,代表当前日志文件中下一条。 •HW:水位或水印(watermark)一词,严格来说,它表示的就是位置信息,即位移(offset) partition 对应的 ISR中最小的 LEO 作为 HW,consumer 最多只能消费到 HW 所在的位置上一条信 息。 •LW:Low Watermark 低水位, 代表 AR 集合中最小的 logStartOffset 值

7、消费者和消费者组有什么关系

一个分区同一个时刻在一个消费组中只能有一个消费者在消费,从而保证消费顺序。消费组中的消费者

的数量不能比一个Topic中的分区的数量多,否则,多出来的消费者消费不到消息。

8、想Kafka消息是采用Pull模式,还是Push模式

Kafka最初考虑的问题是,customer应该从brokes拉取消息还是brokers将消息推送到consumer,也就 是pull还push。在这方面,Kafka遵循了一种大部分消息系统共同的传统的设计:producer将消息推送 到broker,consumer从broker拉取消息。 一些消息系统比如Scribe和Apache Flume采用了push模式,将消息推送到下游的consumer。这样做有 好处也有坏处:由broker决定消息推送的速率,对于不同消费速率的consumer就不太好处理了。消息 系统都致力于让consumer以最大的速率最快速的消费消息,但不幸的是,push模式下,当broker推送 的速率远大于consumer消费的速率时,consumer恐怕就要崩溃了。最终Kafka还是选取了传统的pull 模式。 Pull模式的另外一个好处是consumer可以自主决定是否批量的从broker拉取数据。Push模式必须在不 知道下游consumer消费能力和消费策略的情况下决定是立即推送每条消息还是缓存之后批量推送。如 果为了避免consumer崩溃而采用较低的推送速率,将可能导致一次只推送较少的消息而造成浪费。Pull 模式下,consumer就可以根据自己的消费能力去决定这些策略。 Pull有个缺点是,如果broker没有可供消费的消息,将导致consumer不断在循环中轮询,直到新消息到 达。为了避免这点,Kafka有个参数可以让consumer阻塞直到新消息到达。

9、kafka 的 ack 的三种机制

acks=0:生产者发送数据后就不管了,不会等待broker的ack(确认),这个延迟最低但是存储

的保证最弱当server挂掉的时候就会丢数据

acks=1:生产者会等待ack值,leader确认接收到消息后发送ack(确认)。但是如果leader 挂

掉后他不确保是否复制完成,新leader也会导致数据丢失,可靠性中等,效率中等。

acks=-1(all):生产者会等所有的follower的副本受到数据后才会受到leader 发出的ack(确

认),也即Leader和ISR队列里面所有Follwer应答,可靠性高效率最低

10、Kafka是如何保障数据不丢失的

该问题已经成为了Kafka面试的惯例,如同Java的HashMap,属于高频出现的面试问题。那么,我们该 怎么理解这个问题呢?问题是Kafka如何保障数据不丢失,即Kafka的Broker提供了什么机制保证数据

不丢失的。

其实对于Kafka的Broker而言,Kafka 的复制机制和分区的多副本架构是Kafka 可靠性保证的核心。把消 息写入多个副本可以使Kafka 在发生崩溃时仍能保证消息的持久性。 搞清楚了问题的核心,再来看一下该怎么回答这个问题:主要包括三个方面 1.Topic 副本因子个数:replication.factor >= 3 2.同步副本列表(ISR):min.insync.replicas = 2 3.禁用unclean选举:unclean.leader.election.enable=false 下面将会逐步分析上面的三个配置:

副本因子

Kafka的topic是可以分区的,并且可以为分区配置多个副本,该配置可以通过replication.factor 参 数实现。Kafka中的分区副本包括两种类型:领导者副本(Leader Replica)和追随者副本(Follower Replica),每个分区在创建时都要选举一个副本作为领导者副本,其余的副本自动变为追随者副本。在 Kafka 中,追随者副本是不对外提供服务的,也就是说,任何一个追随者副本都不能响应消费者和生产 者的读写请求。所有的请求都必须由领导者副本来处理。换句话说,所有的读写请求都必须发往领导者 副本所在的 Broker,由该 Broker 负责处理。追随者副本不处理客户端请求,它唯一的任务就是从领导 者副本异步拉取消息,并写入到自己的提交日志中,从而实现与领导者副本的同步。 一般来说,副本设为3可以满足大部分的使用场景,也有可能是5个副本(比如银行)。如果副本因子为N, 那么在N-1个broker 失效的情况下,仍然能够从主题读取数据或向主题写入数据。所以,更高的副本因 子会带来更高的可用性、可靠性和更少的故障。另一方面,副本因子N需要至少N个broker ,而且会有N 个数据副本,也就是说它们会占用N倍的磁盘空间。实际生产环境中一般会在可用性和存储硬件之间作 出权衡。 除此之外,副本的分布同样也会影响可用性。默认情况下,Kafka会确保分区的每个副本分布在不同的 Broker上,但是如果这些Broker在同一个机架上,一旦机架的交换机发生故障,分区就会不可用。所以 建议把Broker分布在不同的机架上,可以使用broker.rack参数配置Broker所在机架的名称。

同步副本列表

In-sync replica(ISR)称之为同步副本,ISR中的副本都是与Leader进行同步的副本,所以不在该列表的 follower会被认为与Leader是不同步的。那么,ISR中存在是什么副本呢?首先可以明确的是:Leader 副本总是存在于ISR中。而follower副本是否在ISR中,取决于该follower副本是否与Leader副本保持了 “同步”。 Kafka的broker端有一个参数replica.lag.time.max.ms, 该参数表示follower副本滞后与Leader副本的 最长时间间隔,默认是10秒。这就意味着,只要follower副本落后于leader副本的时间间隔不超过10 秒,就可以认为该follower副本与leader副本是同步的,所以哪怕当前follower副本落后于Leader副本 几条消息,只要在10秒之内赶上Leader副本,就不会被踢出出局。 可以看出ISR是一个动态的,所以即便是为分区配置了3个副本,还是会出现同步副本列表中只有一个副 本的情况(其他副本由于不能够与leader及时保持同步,被移出ISR列表)。如果这个同步副本变为不可 用,我们必须在可用性和一致性之间作出选择(CAP理论)。 根据Kafka 对可靠性保证的定义,消息只有在被写入到所有同步副本之后才被认为是已提交的。但如果 这里的“所有副本”只包含一个同步副本,那么在这个副本变为不可用时,数据就会丢失。如果要确保已 提交的数据被写入不止一个副本,就需要把最小同步副本数量设置为大一点的值。对于一个包含3 个副 本的主题分区,如果min.insync.replicas=2,那么至少要存在两个同步副本才能向分区写入数据。 如果进行了上面的配置,此时必须要保证ISR中至少存在两个副本,如果ISR中的副本个数小于2,那么 Broker就会停止接受生产者的请求。尝试发送数据的生产者会收到NotEnoughReplicasException异 常,消费者仍然可以继续读取已有的数据。

禁用unclean选举

选择一个同步副本列表{ISR}中的分区作为leader 分区的过程称为clean leader election。注意,这里 要与在非同步副本中选一个分区作为leader分区的过程区分开,在非同步副本中选一个分区作为leader 的过程称之为unclean leader election。由于ISR是动态调整的,所以会存在ISR列表为空的情况,通常 来说,非同步副本落后 Leader 太多,因此,如果选择这些副本作为新 Leader,就可能出现数据的丢 失。毕竟,这些副本中保存的消息远远落后于老 Leader 中的消息。在 Kafka 中,选举这种副本的过程 可以通过Broker 端参数unclean.leader.election.enable控制是否允许 Unclean 领导者选举。开启 Unclean 领导者选举可能会造成数据丢失,但好处是,它使得分区 Leader 副本一直存在,不至于停止 对外提供服务,因此提升了高可用性。反之,禁止 Unclean Leader 选举的好处在于维护了数据的一致 性,避免了消息丢失,但牺牲了高可用性。分布式系统的CAP理论说的就是这种情况。 不幸的是,unclean leader election的选举过程仍可能会造成数据的不一致,因为同步副本并不是完 全同步的。由于复制是异步完成的,因此无法保证follower可以获取最新消息。比如Leader分区的最后 一条消息的offset是100,此时副本的offset可能不是100,这受到两个参数的影响: [replica.lag.time.max.ms]:同步副本滞后与leader副本的时间 [zookeeper.session.timeout.ms]:与zookeeper会话超时时间 简而言之,如果我们允许不同步的副本成为leader,那么就要承担丢失数据和出现数据不一致的风险。 如果不允许它们成为leader,那么就要接受较低的可用性,因为我们必须等待原先的首领恢复到可用状 态。 关于unclean选举,不同的场景有不同的配置方式。对数据质量和数据一致性要求较高的系统会禁用这 种unclean的leader选举(比如银行)。如果在可用性要求较高的系统里,比如实时点击流分析系统,一般 不会禁用unclean的leader选举。

11、什么是“零拷贝”?有什么作用

零拷贝是操作系统提供的一种优化 IO 操作的重要机制。通过零拷贝技术,操作系统可以极大的减少在一 次 IO 操作中,数据从一个内存区域复制到另一个内存区域的次数,以及在此过程中对 CPU 的性能消 耗。零拷贝技术可以极大的提高数据传输的效率,避免不必要的数据拷贝,从而降低系统负载。 零拷贝有两种实现方式,mmap文件映射和sendfile文件复制。 ●mmap机制主要依赖于内存区域映射技术,可以减少一次 IO 操作中,内核态与用户态之间的数据传 输,从而减少因为上下文切换而带来的 CPU 性能开销。mmap机制通常适合于对大量小文件的 IO 操 作,Kafka 大量的运用 mmap 机制加速 Partition 日志文件的读写过程。 ●sendfile主要依赖于 DMA 数据传输技术,采用一组单独的指令集来进行负责数据在内存不同区域之间 的拷贝过程。这样就不再需要 CPU 来进行复制,从而减少 CPU 性能消耗,让 CPU 可以用于更重要的计 算任务。sendfile通常适合于大文件的拷贝传输操作,Kafka 大量的运用 sendfile 机制,加速消息从 Partition 文件到网卡的传输过程。 总之,零拷贝是由操作系统提供的一种高效的文件读写技术,而 Kafka 则大量的运用了零拷贝技术,从 而极大的提升了 Kafka 整体的工作性能。

12、Kafka与RabbitMQ相比有什么优势

Kafka 和 RabbitMQ 都是流行的消息中间件系统,他们各自都有一些优势和适用场景。以下是 Kafka 相 对于 RabbitMQ 的一些比较明显的优势: 1.分布式架构: Kafka 是为大规模分布式流处理而设计的,具有高度可伸缩性。RabbitMQ 虽然也支持 分布式架构,但相对而言,Kafka 的集群设计更完善,更适合处理大规模的消息流。 2.吞吐量: Kafka每秒可处理十几万消息,而 RabbitMQ 每秒可处理几万条消息。 3.消息复制和可用性:Kafka 允许配置多个消息副本,确保数据的冗余存储,提高可用性和容错性。 RabbitMQ 也支持镜像队列以实现冗余,但是不如 Kafka 的多副本复制灵活。 4.时间溯源:Kafka 在事件溯源和事件驱动架构中非常强大。他允许事件在 Topic 中保留一段时间,以 便后续的分析和回溯查询。RabbitMQ 通常用于实时消息传递,对于事件溯源不够灵活。 5.批处理和流处理: Kafka 提供了流处理 API,课用于实时数据流处理等场景。而 RabbitMQ 倾向于更 专注的处理实时消息传递。 6.社区和生态系统:Kafka 有一个庞大的社区和丰富的生态系统,提供了许多与大数据和流处理相关的 工具和库。RabbitMQ 也有一个活跃的社区,但是相对而言社区规模以及社区活跃性就要小很多。 如果您需要处理大规模的实时数据流或事件驱动架构,Kafka 可能更适合;如果您更关注传统的消息传 递和队列处理,RabbitMQ 的高级功能更丰富,可能更合适。因此,选择哪种消息中间件还是要取决于 具体的应用场景。

13、Kafka中的Topic和Partition有什么关系

在Kafka中,Topic和Partition是两个密切相关的概念。 ●Topic是Kafka中消息的逻辑分类,可以看作是一个消息的存储类别。它是按照不同的主题对消息进行 分类,并且可以用于区分和筛选数据。每个Topic可以有多个Partition,每个Partition都是Topic的一个 子集,包含了一部分特定的消息。 ●Partition则是Kafka 中实际保存数据的单位。每个Topic可以被划分为多个Partition,而这些 Partition 会尽量平均的分配到各个 Broker 上。当一条消息发送到Kafka时,它会被分配到一个特定的Partition 中,并最终写入 Partition 对应的日志文件里。这个分配过程是根据Partition的规则来完成的,比如可以 按照消息的某个属性进行哈希或者按照时间戳进行排序等。 因此,Topic和Partition的关系是,Topic是消息的逻辑分类,用于区分和筛选数据,而Partition则是 Topic的物理划分,用于将消息分配到不同的部分中以便于处理和存储。Topic 和 Partition 的设计对于 高吞吐量和横向扩展非常有用。因为生产者和消费者只需要根据 Topic 进行具体的业务实现,而不用关 心消息在集群内的分布情况。而在集群内部,这些 Partition 会尽量平均的分布在不同的 Broker节点 上,从而提高了系统整体的性能和可伸缩性。

14、Kafka的消费消息是如何传递的

在Kafka中,消息的传递主要涉及三个环节:生产者生产消息、broker保存消息和消费者消费消息。 1生产者生产消息:生产者负责将消息发布到Kafka broker。在发布消息时,生产者需要指定目标主题。 消息被写入后,将被存储在指定分区的当前副本中。当发送消息失败时,生产者还会提供确认以及重试 机制,以保证消息能够正确的发送到 Broker 上。 2broker保存消息:Kafka broker接收到生产者发送的消息后,会将其存储在内部的缓冲区中,等待消费 者拉取。当消费者向broker发送拉取请求时,broker会从缓冲区中获取消息并返回给消费者。Kafka broker能够保证消息的可靠性和顺序性,即使在异常情况下(如服务器崩溃),也能够保证消息不会丢 失。 3消费者消费消息:消费者从Kafka broker中订阅指定的主题,并拉取消息进行消费。消费者可以以同步 或异步的方式拉取消息,并对拉取到的消息进行处理。当消费者处理完消息后,会向Kafka broker发送 确认消息,表示消息已经被成功处理。这样可以保证消息被正确处理且不会重复消费。 总体来说,Kafka通过生产者、Kafka broker和消费者的协同工作,实现了高吞吐量、高可靠性和高可扩 展性的消息传递。

15、如何确保Kafka集群的高可用

Kafka设计了多种机制,共同保证集群的高可用性: 1分布式架构:Kafka集群通常由多个Broker组成,每个Broker存储部分数据副本。这样,即使某个 Broker出现故障,其他Broker也可以继续处理和存储消息,从而保证整体的高可用性。 2数据冗余:Kafka通过数据冗余来保证高可用性。每个Topic的数据会被分成多个Partition,并在多个 Broker上进行复制。即使某个Broker出现故障,数据仍然可以从其他Broker中获取。 3副本机制:副本是Kafka实现高可用性的重要手段。Kafka中的每个Partition都有多个副本,这些副本 分布在不同的Broker上,从而在部分Broker故障时,仍然有足够的副本可用以保证高可用性。 4分区领导者选举:在Kafka中,每个Partition都有一个领导者(Leader)和零个或多个追随者 (Follower)。当领导者不可用时,追随者会进行领导者选举,以保证系统的可用性。 5消费者组实现负载均衡:Kafka的消费者可以组成消费者组,通过消费者组,可以将负载均匀地分配到 多个消费者上,从而避免单个消费者的性能瓶颈,提高整个Kafka集群的可用性。 6故障检测和恢复: Kafka 会使用 Zookeeper 等组件协助监控和管理集群的状态。当检测到故障节点 时,就会自动将不可用的节点从集群中排除。而等到故障节点恢复后,也会重新将节点加入到集群当 中。 集群高可用性是 Kafka非常关键的设计之一。通过多项机制组合,使得 Kafka 可以成为处理关键业务数 据的可信平台。

16、Kafka中的消费者偏移量是如何管理的

在Kafka中,消费者偏移量是指消费者在处理消息过程中所处的位置。Kafka中的消费者偏移量由两部分 组成:Topic和Partition。对于每个消费者组,Kafka都会为其维护在每个 Partition 上的偏移量,以便在 处理消息时可以准确地跟踪进度。 消费者偏移量的管理可以通过以下方式进行: 1手动提交偏移量:消费者可以通过调用commitSync或commitAsync方法手动提交偏移量到Kafka。手 动提交偏移量的方式需要开发者在适当的时机调用提交方法,确保消费者处理完消息后再提交偏移量。 这种方式对于灵活性和精确控制偏移量非常有用,但需要开发者自行考虑提交的时机和异常处理。 2自动提交偏移量:消费者可以配置为在后台自动提交偏移量。这意味着消费者会定期自动将已经处理的 消息的偏移量提交给Kafka,而不需要开发者手动处理。通过配置参数enable.auto.commit为true,以 及设置auto.commit.interval.ms参数来控制自动提交的频率。自动提交偏移量简化了管理,但可能会导 致消息的重复处理或丢失,因此需要根据具体业务场景谨慎配置。 总之,Kafka 消费者的偏移量管理是确保消息传递的可靠性和一致性的重要部分。它允许消费者灵活地 管理消息的消费进度,以满足不同的应用需求。无论您选择自动还是手动管理偏移量,都需要确保偏移 量的正确提交,以避免消息的重复消费。

17、Kafka中的消息如何分配给不同的消费者

Kafka中的消息是通过分区(Partition)分配给不同的消费者的。Kafka将每个Topic划分为多个 Partition,每个Partition存储一部分消息。消费者通过订阅Topic来消费消息,而Kafka将Partition中的 消息按照一定的分配策略分配给消费者组中的不同消费者。 Kafka提供了多种分区分配策略,用于确定如何将分区分配给消费者。例如: 1RoundRobin 轮询策略:Kafka将Partition按照轮询的方式分配给消费者组中的不同消费者,每个消费 者依次获得一个Partition,直到所有Partition被分配完毕。当消费者数量发生变化时,Kafka会重新分配 Partition。 2Range 范围策略:Kafka将Partition按照Range的方式分配给消费者组中的不同消费者,每个消费者负 责处理指定范围内的Partition。这种分配方式适用于Topic的Partition数量较少,而消费者数量较多的情 况。 3Sticky 粘性策略:尽量保持每个消费者在一段时间内消费相同的分区,以减少分区重新分配的频率 当消费者处理完一个Partition中的所有消息后,它会向Kafka发送心跳请求,Kafka会将该Partition分配 给其他消费者进行处理。这种机制确保了消息在不同的消费者之间负载均衡,并提高了容错性。如果一 个消费者出现故障,其他消费者可以继续处理Partition中的消息,而不会导致消息丢失或重复处理。

18、Kafka中的消息是如何存储的

Kafka 中的消息是以文件的方式持久化到磁盘中进行存储的,这是 Kafka 的一个关键特性,确保消息的 可靠性和可用性。Kafka中的消息是通过以下方式进行存储的: 1Partition 分区:Partition是Kafka中消息存储的基本单位,每个Topic下的消息都会被划分成多个 Partition进行管理。每个Partition都是一个有序的、不变的消息队列,消息按照追加的顺序被添加到队 列尾部。 2Segment 分块:Partition会被进一步划分成多个Segment,Segment是逻辑上的文件组,方便进行数 据的管理和查找。每个Segment里都包含多个文件,这些文件名相同且被集合在一起。 3文件索引:Segment中的每个文件都有自己的索引文件和数据文件,索引文件存储了当前数据文件的 索引信息,而数据文件则存储了当前索引文件名对应的数据信息。 4消息偏移:Kafka中的每个消息都会被分配到一个特定的Partition中,然后根据Partition内的Segment 划分,被存储到对应的数据文件中。消息的偏移量信息则会被记录在索引文件中。 5持久化:Kafka中的每个消息都包含一个64位的偏移量,该偏移量表示消息在Partition中的位置。当消 费者读取消息时,可以通过偏移量信息来确定需要从哪个位置开始读取。 Kafka 的消息存储是基于日志文件和分区的,确保了消息的可靠性、持久性和高吞吐量。消息被追加到 日志文件中,每个消息都有唯一的偏移量,分区和副本机制保证了数据的冗余存储和可用性。这种设计 使 Kafka 成为一个可信赖的消息传递系统,适用于各种实时数据处理、日志聚合和事件驱动应用程序。

19、Kafka其他常见问题

1、什么是消息队列与Kafka简介

消息队列(Message Queue,简称MQ),指保存消息的一个容器,本质是个队列。 消息(Message)是指在应用之间传送的数据,消息可以非常简单,比如只包含文本字符串,也可以更 复杂,可能包含嵌入对象。 消息队列(Message Queue)是一种应用间的通信方式,消息发送后可以立即返回,有消息系统来确保 信息的可靠专递,消息发布者只管把消息发布到MQ中而不管谁来取,消息使用者只管从MQ中取消息而 不管谁发布的,这样发布者和使用者都不用知道对方的存在。 Producer:消息生产者,负责产生和发送消息到 Broker; Broker:消息处理中心。负责消息存储、确认、重试等,一般其中会包含多个 queue; Consumer:消息消费者,负责从 Broker 中获取消息,并进行相应处理;

2、为什么需要消息队列?

1、屏蔽异构平台的细节:发送方、接收方系统之间不需要了解双方,只需认识消息。 2、异步:消息堆积能力;发送方接收方不需同时在线,发送方接收方不需同时扩容(削峰)。 3、解耦:防止引入过多的API给系统的稳定性带来风险;调用方使用不当会给被调用方系统造成压力, 被调用方处理不当会降低调用方系统的响应能力。 4、复用:一次发送多次消费。 5、可靠:一次保证消息的传递。如果发送消息时接收者不可用,消息队列会保留消息,直到成功地传递 它。 6、提供路由:发送者无需与接收者建立连接,双方通过消息队列保证消息能够从发送者路由到接收者, 甚至对于本来网络不易互通的两个服务,也可以提供消息路由。

3、消息队列有什么优点和缺点?

  1. 核心优点
  2. 解耦
  3. 异步
  4. 削峰
  5. 缺点
  6. 系统可用性降低:系统引入的外部依赖越多,越容易挂掉。
  7. 系统复杂度提高了
  8. 一致性问题:消息传递给多个系统,部分执行成功,部分执行失败,容易导致数据不一致

4、Kafka的优势和特点

高吞吐量:单机每秒处理几十上百万的消息量。即使存储了许多TB的消息,它也保持稳定的性能。 高性能:单节点支持上千个客户端,并保证零停机和零数据丢失,异步化处理机制 持久化:将消息持久化到磁盘。通过将数据持久化到硬盘以及replica(follower节点)防止数据丢 失。 零拷贝:减少了很多的拷贝技术,以及可以总体减少阻塞事件,提高吞吐量。 可靠性:Kafka是分布式,分区,复制和容错的。 Kafka的特点:

顺序读,顺序写

利用Linux的页缓存

分布式系统,易于向外扩展。所有的Producer、Broker和Consumer都会有多个,均为分布式

的。无需停机即可扩展机器。多个Producer、Consumer可能是不同的应用。

客户端状态维护:消息被处理的状态是在Consumer端维护,而不是由server端维护。当失败时能

自动平衡。

支持online(在线)和offline(离线)的场景。

支持多种客户端语言。Kafka支持Java、.NET、PHP、Python等多种语言。

5、Kafka与传统消息队列的对比

各种对比之后,有如下建议: ActiveMQ,没经过大规模吞吐量场景的验证,社区也不是很活跃,所以不推荐; RabbitMQ,虽然erlang 语言阻止了大量的 Java 工程师去深入研究和掌控它,对公司而言,几乎处 于不可控的状态,但是毕竟是开源的,比较稳定的支持,活跃度也高,推荐中小型公司使用;推荐 RocketMQ,阿里出品,Java语言编写,经过了阿里多年双十一大促的考验,性能和稳定性得到了 充分的严重。目前在业界被广泛应用在订单,交易,充值,流计算,消息推送,日志流式处理, binlog分发等场景;强烈推荐 Kafka,如果是大数据领域的实时计算、日志采集等场景,用 Kafka 是业内标准的,绝对没问题, 社区活跃度很高,绝对不会黄,何况几乎是全世界这个领域的事实性规范。

6、Kafka的架构设计

kafka运行在集群上,集群包含一个或多个服务器。kafka把消息存在topic中,每一条消息包含键值 (key),值(value)和时间戳(timestamp)。 kafka有以下一些基本概念: Producer - 消息生产者,就是向kafka broker发消息的客户端。 Consumer - 消息消费者,是消息的使用方,负责消费Kafka服务器上的消息。 Topic - 主题,由用户定义并配置在Kafka服务器,用于建立Producer和Consumer之间的订阅关系。生 产者发送消息到指定的Topic下,消息者从这个Topic下消费消息。 Partition - 消息分区,一个topic可以分为多个 partition,每个partition是一个有序的队列。partition 中的每条消息都会被分配一个有序的id(offset)。 Broker - 一台kafka服务器就是一个broker。一个集群由多个broker组成。一个broker可以容纳多个 topic。 Consumer Group - 消费者分组,用于归组同类消费者。每个consumer属于一个特定的consumer group,多个消费者可以共同消息一个Topic下的消息,每个消费者消费其中的部分消息,这些消费者就 组成了一个分组,拥有同一个分组名称,通常也被称为消费者集群。 Offset - 消息在partition中的偏移量。每一条消息在partition都有唯一的偏移量,消息者可以指定偏移 量来指定要消费的消息。

7、工作流程

producer先从zookeeper的 “/brokers/…/state”节点找到该partition的leader producer将消息发送给该leader leader将消息写入本地log followers从leader pull消息 写入本地log后向leader发送ACK leader收到所有ISR中的replication的ACK后,增加HW(high watermark,最后commit 的 offset)并向producer发送ACK tips Kafka 中消息是以topic 进行分类的,生产者生产消息,消费者消费消息,都是面向topic的。 topic 是逻辑上的概念,而partition 是物理上的概念,每个partition 对应一个log 文件,该log 文 件中存储的就是producer 生产的数据。Producer 生产的数据会被不断追加到该log 文件末端,且 每条数据都有自己的offset。消费者组中的每个消费者,都会实时记录自己消费到了哪个offset, 以便出错恢复时,从上次的位置继续消费。

8、Kafka的数据模型与消息存储机制

消息存储结构

Kafka 有 Topic 和 Partition 两个概念,一个 Topic 可以有多个 Partition。在实际存储的时候,Topic + Partition 对应一个文件夹,这个文件夹对应的是这个 Partition 的数据。 在 Kafka 的数据文件目录下,一个 Partition 对应一个唯一的文件夹。如果有 4 个 Topic,每个 Topic 有 5 个 Partition,那么一共会有 4 * 5 = 20 个文件夹。而在文件夹下,Kafka 消息是采用 Segment File 的存储方式进行存储的。 Segment File 的大概意思是:将大文件拆分成小文件来存储,这样一个大文件就变成了一段一段 (Segment 段)。这样的好处是 IO 加载速度快,不会有很长的 IO 加载时间。Kafka 的消息存储就采用 了这种方式。 如上图所示,在一个文件夹下的数据会根据 Kafka 的配置拆分成多个小文件。拆分规则可以根据文件大 小拆分,也可以根据消息条数拆分,这个是 Kafka 的一个配置,这里不细说。 在 Kafka 的数据文件夹下,分为两种类型的文件:索引文件(Index File)和数据文件(Data File)。索 引文件存的是消息的索引信息,帮助快速定位到某条消息。数据文件存储的是具体的消息内容。

索引文件

索引文件的命名统一为数字格式,其名称表示 Kafka 消息的偏移量。我们假设索引文件的数字为 N,那 么就代表该索引文件存储的第一条 Kafka 消息的偏移量为 N + 1,而上个文件存储的最后一条 Kafka 消 息的偏移量为 N(因为 Kafka 是顺序存储的)。例如下图的 368769.index 索引文件,其表示文件存储 的第一条 Kafka 消息的偏移量为 368770。而 368769 表示的是 0000.index 这个索引文件的最后一条消 息。所以 368769.index 索引文件,其存储的 Kafka 消息偏移量范围为 368769-737337。 索引文件存储的是简单地索引数据,其格式为:「N,Position」。其中 N 表示索引文件里的第几条消 息,而 Position 则表示该条消息在数据文件(Log File)中的物理偏移地址。例如下图中的「3,497」表 示:索引文件里的第 3 条消息(即 offset 368772 的消息,368772 = 368769+3),其在数据文件中的 物理偏移地址为 497。 其他的以此类推,例如:「8,1686」表示 offset 为 368777 的 Kafka 消息,其在数据文件中的物理偏移 地址为 1686。

数据文件

数据文件的命名格式与索引文件的命名格式完全一样,这里就不再赘述了。 通过上面索引文件的分析,我们已经可以根据 offset 快速定位到某个数据文件了。那接着我们怎么读取 到这条消息的内容呢?要读取到这条消息的内容,我们需要搞清楚数据文件的存储格式。 数据文件就是所有消息的一个列表,而每条消息都有一个固定的格式,如下图所示。 从上图可以看到 Kafka 消息的物理结构,其包含了 Kafka 消息的 offset 信息、Kafka 消息的大小信息、 版本号等等。有了这些信息之后,我们就可以正确地读取到 Kafka 消息的实际内容。

9、Kafka文件存储优势

Kafka运行时很少有大量读磁盘的操作,主要是定期批量写磁盘操作,因此操作磁盘很高效。这跟Kafka 文件存储中读写message的设计是息息相关的。Kafka中读写message有如下特点:

写message

消息从java堆转入page cache(即物理内存)。 由异步线程刷盘,消息从page cache刷入磁盘。

读message

消息直接从page cache转入socket发送出去。 当从page cache没有找到相应数据时,此时会产生磁盘IO,从磁盘Load消息到page cache,然后直 接从socket发出去

10、Kafka高效文件存储设计特点

Kafka把topic中一个parition大文件分成多个小文件段,通过多个小文件段,就容易定期清除或删 除已经消费完文件,减少磁盘占用。 通过索引信息可以快速定位message和确定response的最大大小。 通过index元数据全部映射到memory,可以避免segment file的IO磁盘操作。 通过索引文件稀疏存储,可以大幅降低index文件元数据占用空间大小。

11、Kafka 副本同步机制

为保证producer发送的数据,能可靠到指定topic,topic的每个的partition收到 producer发送的数据 后,都需要向producer发送 ack(acknowledgement确认收到),如果 producer收到 ack,就会进行 下一轮的发送。

ACKS 机制

在 Kafka 中,消息的 ACK(Acknowledgment,确认)机制与生产者的acks配置有关。acks配置表示 生产者在接收到消息后等待副本同步确认的方式,具体取值有:

acks=0:

意义:生产者在成功将消息发送给 Kafka 服务端后不等待任何确认。 结果:生产者无法知道消息是否成功到达 Kafka 服务器,可能会导致消息的丢失。这种配置下,生 产者不会收到任何 ACK。

acks=1:

意义:生产者在成功将消息发送给 Kafka 服务端后,等待该分区的首领节点(leader)确认。 结果:生产者会收到分区首领节点的 ACK。这意味着只要分区首领节点成功接收到消息,生产者就 会得到确认,而不需要等待其他副本。

acks=all 或 acks=-1:

意义:生产者在成功将消息发送给 Kafka 服务端后,等待所有分区副本确认。 结果:生产者会等待分区的所有副本都成功接收到消息并确认。这是最安全的配置,因为只有当所 有副本都确认接收到消息后,才认为消息被成功提交。

生产者重试机制:

Kafka 生产者在发送消息后,如果设置了等待服务器的确认(通过acks参数配置),会等待一定时间来 收到来自服务器的确认(ack)。这个等待时间由timeout.ms参数控制,默认是10000毫秒(10 秒)。 如果在等待时间内没有收到服务器的确认,生产者可以选择重试发送或者处理发送失败的逻辑。这取决 于生产者的配置。通常,生产者会根据配置的重试次数和重试间隔来进行重试,以确保消息最终被成功 发送。 在 Kafka 的生产者配置中,你可以找到以下与重试相关的配置项: retries: 定义了生产者在发送消息时的最大重试次数。 retry.backoff.ms: 定义了两次重试之间的等待时间间隔。

ISR 机制:

Kafka根据副本同步的情况,分成了3个集合: AR(Assigned Replicas):包括ISR和OSR ISR(In-sync Replicas):和leader副本保持同步的副本集合,可以被认为是可靠的数据 OSR(Out-Sync Replicas):和Leader副本同步失效的副本集合 当 kafka 副本同步机制是所有follower都同步成功才返回 ack 给生产者时,如果有一个follower,因为 某种故障,迟迟不能与leader 进行同步,那leader 就要一直等下去,直到它完成同步,才能发送ack。 这个问题怎么解决呢? Leader维护了一个动态的in-sync replica set (ISR-同步副本列表),意为和leader保持同步的follower集 合。根据follower发来的FETCH请求中的fetch offset判断ISR中的follower完成数据同步是否成功。如果 follower长时间未向leader同步数据,则该follower将被踢出ISR,该时间阈值由 replica.lag.time.max.ms参数设定。Leader发生故障之后,就会从ISR中选举新的leader。 ISR(In-Sync Replicas ):与leader保持同步的follower集合 AR(Assigned Replicas):分区的所有副本 ISR是由leader维护,follower从leader同步数据有一些延迟(包括延迟时间 replica.lag.time.max.ms和延迟条数replica.lag.max.messages两个维度, 当前最新的版本0.10.x中 只支持replica.lag.time.max.ms这个维度),任意一个超过阈值都会把follower剔除出ISR,存入 OSR(Outof-Sync Replicas)列表,新加入的follower也会先存放在OSR中。 AR=ISR+OSR。

12、Kafka 副本数据一致性

尽管采用 acks = all 但是也会出现不一致的场景,例如: 假设 leader 接受了 producer 传来的数据为 8 条,ISR 中三台 follower(broker0,broker1,broker2)开 始同步数据,由于网络传输,另外两台 follower 同步数据的速率不同。当 broker1 同步了 4 条数据, broker2 已经同步了 6 条数据,此时,leader-broker0 突然挂掉,从 ISR 中选取了 broker1 作为主节 点,此时 leader-broker1 同步了 4 条,broker2 同步 6,就会造成 leader 和 followe r之间数据不一致 问题。 HW(High Watermark)俗称高水位,它标识了一个特定的消息偏移量(offset),消费者只能拉 取到这个offset之前的消息,对于同一个副本对象而言,其 HW 值不会大于 LEO 值。小于等于 HW 值的所有消息都被认为是“已备份”的(replicated)。所有分区副本中消息偏移量最小值。 LEO(Log End Offset),即日志末端位移(log end offset),记录了该副本底层日志(log)中下一条 消息的位移值。注意是下一条消息!也就是说,如果 LEO =8,那么表示该副本保存了 8 条消息, 位移值范围是[0, 7]。LEO 的大小相当于当前日志分区中最后一条消息的 offset 值加1,分区 ISR 集 合中的每个副本都会维护自身的 LEO,而ISR 集合中最小的 LEO 即为分区的 HW,对消费者而言 只能消费 HW 之前的消息。

针对不同的产生原因,解决方案不同:

当服务出现故障时:如果是 Follower 发生故障,这不会影响消息写入,只不过是少了一个备份而 已。处理相对简单一点。Kafka 会做如下处理: 将故障的 Follower 节点临时踢出 ISR 集合。而其他 Leader 和 Follower 继续正常接收消息。 出现故障的 Follower 节点恢复后,不会立即加入 ISR 集合。该 Follower 节点会读取本地记录的上 一次的 HW,将自己的日志中高于 HW 的部分信息全部删除掉,然后从 HW 开始,向 Leader 进行 消息同步。 等到该 Follower 的 LEO 大于等于整个 Partiton 的 HW 后,就重新加入到 ISR 集合中。这也就是说 这个 Follower 的消息进度追上了 Leader。 如果是 Leader 节点出现故障,Kafka 为了保证消息的一致性,处理就会相对复杂一点。 Leader 发生故障,会从 ISR 中进行选举,将一个原本是 Follower 的 Partition提升为新的 Leader。这时,消息有可能没有完成同步,所以新的 Leader 的LEO 会低于之前 Leader 的 LEO。 Kafka 中的消息都只能以 Leader 中的备份为准。其他 Follower 会将各自的Log 文件中高于 HW 的 部分全部清理掉,然后从新的 Leader 中同步数据。 旧的 Leader 恢复后,将作为 Follower 节点,进行数据恢复。

六、ElasticSearch

1、为什么要使用ElasticSearch

使用Elasticsearch有以下几个主要原因:

  1. 强大的全文搜索功能:Elasticsearch是一个基于Lucene的分布式搜索引擎,具有强大的全文搜索 和检索功能。它支持复杂的查询语法和多种搜索方式,能够高效地处理大量的文本数据。这使得它 非常适合用于构建搜索引擎、日志分析、内容推荐等应用。
  2. 分布式和高可用性:Elasticsearch是一个分布式系统,它能够将数据分布在多个节点上进行存储和 计算。这使得它具有高可用性和横向扩展能力,能够处理大规模数据和高并发访问。它还支持故障 转移和自动数据恢复,即使某个节点发生故障,系统仍然可以继续运行。
  3. 实时数据分析和聚合:Elasticsearch提供了强大的聚合功能,能够对大规模数据进行实时的数据分 析和统计。它支持各种聚合操作,如求和、平均值、最大最小值、分组计数等,能够快速生成各种 报表和可视化图表,帮助用户更好地理解和利用数据。
  4. 构建实时应用:Elasticsearch具有低延迟的写入和查询能力,能够快速响应实时数据的变化。这使 得它非常适合用于构建实时监控、实时推荐、实时搜索等实时应用场景。
  5. 生态系统和易用性:Elasticsearch拥有丰富的生态系统,包括Kibana用于数据可视化、Logstash 用于日志收集和处理、Beats用于数据采集等。它还提供了RESTful API和丰富的客户端库,使得开 发和集成变得非常简单和灵活。 综上所述,Elasticsearch因其强大的全文搜索能力、分布式和高可用性特性、实时数据分析能力以及丰 富的生态系统而成为一种流行的选择,适用于各种大规模数据处理和实时应用场景。

2、ElasticSearch为什么快

Elasticsearch之所以快速,主要有以下几个原因:

  1. 倒排索引(Inverted Index):Elasticsearch使用倒排索引来加速搜索过程。倒排索引是一种将词 条与其出现位置的映射关系存储在索引中的数据构。通过倒排索引,Elasticsearch可以快速定位包 含特定词条的文档,从而加搜索效率。
  2. 分布式架构:Elasticsearch是一个分布式系统,它可以将数据分布在多个节点上进行存储和计算。 这使得它具有横向扩展能力,能够处理大规模数据和高并发访问。通过将数据分片和分布式查询, Elasticsearch可以并行处理搜索请求,从而提高搜索效率。
  3. 倒排索引的内存缓存:Elasticsearch使用内存缓存来提高搜索性能。它会将热门的词条和搜索结果 存储在内存中,以便快速响应查询请求。通过利用内存缓存,Elasticsearch可以避免频繁的磁盘访 问而加速搜索速度。
  4. 集群化部署和负载均衡:Elasticsearch支持集群化部署,可以将索引和查询请求分布在多个节点 上。它还提供了负载均衡机制,能够均衡地将请求分发给不同的节点,从而提高整体的处理能力和 响应速度。
  5. 后台实时刷新机制:Elasticsearch采用了近实时(Near Real-time)的刷新机制。当数据写入到 Elasticsearch时,它会先写入内存缓冲区,然后异步地刷新到磁盘。这样可以避免频繁地磁盘写 入,同时保证数据的可靠性和一致性。 综上所述,Elasticsearch通过倒排索引、分布式架构、内存缓存、负载均衡和实时刷新机制等技术手 段,实现了高效的搜索和查询功能,从而使其具备快速响应和处理大规模数据的能力。

3、倒排索引是什么

倒排索引(Inverted Index)是一种将词条与其出现位置的映射关系存储在索引中的数据结构。它是一 种反转(倒排)了文档-词条关系的索引方式。通常,一个正向索引(Forward Index)是通过文档ID来 查找对应的词条,而倒排索引则是通过词条来查找对应的文档ID。 在倒排索引中,每个词条都会关联一个或多个文档。对于每个词条,倒排索引会记录下它在哪些文档中 出现过以及出现的位置。这样,当需要搜索某个词条时,可以快速地找到包含该词条的文档。 倒排索引通常由两个主要部分组成:词典(Dictionary)和倒排列表(Inverted List)。词典以词条为 键,记录了每个词条对应的倒排列表的位置。而倒排列表则包含了所有包含该词条的文档ID以及出现位 置的信息。 倒排索引的结构使得搜索引擎能够快速定位包含特定词条的文档,从而加快搜索的速度。它是搜索引擎 中常用的索引结构,被广泛应用于全文搜索、文本分析和信息检索等领域。

4、如何保证ES和数据库的数据一致性

为了保证Elasticsearch(ES)和数据库的数据一致性,可以采取以下几种方式:

  1. 实时同步:在数据更新到数据库之后,立即将相应的数据同步到ES中。可以通过在应用程序的业务 逻辑中,同时更新数据库和ES来实现实时同步。这样可以保证数据库和ES中的数据始终保持一致。
  2. 延迟同步:在数据更新到数据库之后,通过定时任务或消息队列等机制,定期批量将更新的数据同 步到ES中。这种方式可以降低对数据库写入操作的性能影响,并在一定程度上保证数据的同步性。
  3. 双写模式:在数据更新时,同时向数据库和ES进行写入操作。这种方式可以确保数据同时写入到两 个存储系统,从而保证数据的一致性。但是需要注意双写模式可能增加系统的复杂度和写入延迟。
  4. 使用数据库的日志或触发器:数据库的日志或触发器可以捕获数据的变更操作,然后通过监听这些 变更操作,将数据同步到ES中。这种方式可以避免对应用程序进行大量修改,但需要考虑数据库和 ES之间的网络延迟和数据同步的顺序。 无论采取哪种方式,都需要确保数据库和ES的数据操作是原子的、可靠的,并且在故障恢复时能够保持 数据的一致性。此外,还可以通过监控和日志记录来及时发现和解决数据同步的问题,确保数据的一致 性和可靠性。

5、ElasticSearch深度分页解决方案

1、什么是深度分页(Deep paging)?

1.1 ES中from+size 分页 分页问题是Elasticsearch中最常见的查询场景之一,正常情况下分页代码如实下面这样的: 很好理解,即查询第一页的5 条数据。图中数字2即返回的五条文档数据。但是如果我们查询的数据页数 特别大,达到什么程度呢?当from + size 大于10000 的时候,就会出现问题

GET order_2290w/_search
{
"from": 0,
"size": 5
}

报错信息的解释为当前查询的结果超过了10000的最大值。那么疑问就来了,明明只查询了5条数据,为 什么它计算最大值要加上我from的数量呢?而且Elasticsearch不是号称PB及数据秒级查询,几十亿的数 据都没问题,怎么还限制最大查询前10000条数据呢?这里有一个字很关键:“前”,前10000条意味着什 么?意味着数据肯定是按照某种顺序排列的,ES中如果不人工指定排序字段,那么最终结果将按照相关 度评分排序。 分布式系统都面临着同一个问题,数据的排序不可能在同一个节点完成。一个简单的需求,比如:

1.2 案例解释什么是深分页

从10万名高考生中查询成绩为的10001-10100 位的100 名考生的信息。 看似简单的查询其实并不简单,我们来画图解释一下: 假设10万名考生的考试信息被存放在一个exam_info索引中,由于索引数据在写入是并无法判断在执行 业务查询时的具体排序规则,因此排序是随机的。而由于ES的分片和数据分配策略为了提高数据在检索 时的准确度,会把数据尽可能均匀的分布在不同的分片。假设此时我们有五个分片,每个分片中承载2万 条有效数据。按照需求我们需要去除成绩在10001到10100的一百名考生的信息,就要先按照成绩进行 倒序排列。然后按照page_size: 100&page_index: 101进行查询。即查询按照成绩排序,第101页的100 位学员信息。 单机数据库的查询逻辑很简单,先按照把10万学生成绩排序,然后从前10100条数据数据中取出第 10001-10100条。即按照100为一页的第101页数据。 但是分布式数据库不同于单机数据库,学员成绩是被分散保存在每个分片中的,你无法保证要查询的这 一百位学员的成绩一定都在某一个分片中,结果很有可能是存在于每个分片。换句话说,你从任意一个 分片中取出的前10100位学员的成绩,都不一定是总成绩的前10100。更不幸的是,唯一的解决办法是 从每个分片中取出当前分片的前10100名学员成绩,然后汇总成50500条数据再次排序,然后从排序后 的这50500个成绩中查询前10100的成绩,此时才能保证一定是整个索引中的成绩的前10100名。 如果还不理解,我再举个例子用来类比:从保存了世界所有国家短跑运动员成绩的索引中查询短跑世界 前三,每个国家类比为一个分片的数据,每个国家都会从国家内选出成绩最好的前三位参加最后的竞 争,从每个国家选出的前三名放在一起再次选出前三名,此时才能保证是世界的前三名。

2、深度分页会带来什么问题?

从上面案例中不难看出,每次有序的查询都会在每个分片中执行单独的查询,然后进行数据的二次排 序,而这个二次排序的过程是发生在heap中的,也就是说当你单次查询的数量越大,那么堆内存中汇总 的数据也就越多,对内存的压力也就越大。这里的单次查询的数据量取决于你查询的是第几条数据而不 是查询了几条数据,比如你希望查询的是第10001-10100这一百条数据,但是ES必须将前10100全部取 出进行二次查询。因此,如果查询的数据排序越靠后,就越容易导致OOM(Out Of Memory)情况的 发生,频繁的深分页查询会导致频繁的FGC。 ES为了避免用户在不了解其内部原理的情况下而做出错误的操作,设置了一个阈值,即 max_result_window,其默认值为10000,其作用是为了保护堆内存不被错误操作导致溢出。因此也就 出现了文章一开始所演示的问题。

3、max_result_window参数

max_result_window是分页返回的最大数值,默认值为10000。max_result_window本身是对JVM的一 种保护机制,通过设定一个合理的阈值,避免初学者分页查询时由于单页数据过大而导致OOM。 在很多业务场景中经常需要查询10000条以后的数据,当遇到不能查询10000条以后的数据的问题之 后,网上的很多答案会告诉你可以通过放开这个参数的限制,将其配置为100万,甚至1000万就行。但 是如果仅仅放开这个参数就行,那么这个参数限制的意义有何在呢?如果你不知道这个参数的意义,很 可能导致的后果就是频繁的发生OOM而且很难找到原因,设置一个合理的大小是需要通过你的各项指标 参数来衡量确定的,比如你用户量、数据量、物理内存的大小、分片的数量等等。通过监控数据和分析 各项指标从而确定一个最佳值,并非越大约好。

4、深度分页问题的常见解决方案?

4.1 尝试避免深度分页

目前人类对抗疾病最有效的手段:打疫苗。没错,能方式发生的问题总比发生之后再治理来的强。同 样,解决深度分页问题最好的办法也是预防,也就是能避免最好是避免使用深度分页。我相信不服气的 小伙儿伴已经满嘴质疑了,我们怎么能要求用户去做什么、不做什么呢?用户想深度分页检索你凭什么 不让呢?技术要服务于业务!不能用妥协用户体验来解决技术问题… 带着这些质疑,我们先来看一看众多大型搜索引擎面对深度分页问题是如何处理的: 首先是以百度和谷歌为代表的全文搜索引擎: 谷歌、百度目前作为全球和国内做大的搜索引擎。不约而同的在分页条中删除了“跳页”功能,其目的就 是为了避免用户使用深度分页检索。难道删除“跳页”就能阻止用户查询很多页以后的数据了吗?我直接 狂点下一页不也是深度分页?好我暂时先不反驳这里的提问,但是我也发出一个反问,至少删除跳页, 可以阻挡哪些刻意去尝试深度分页的“恶意用户”,真正想通过搜索引擎来完成自己检索需求的用户,通 常来说都会首先查看第一页数据,因为搜索引擎是按照“相关度评分”进行排名的,也就是说,第一页的 数据很往往是最符合用户预期结果的。 下面我们再看一下以中国最大电商平台“淘宝”为代表的垂直搜索引擎是怎么解决的: 我们分别尝试搜索较大较为宽泛的商品种类,以使其召回结果足够多(这里以手机和衣服为例)。 虽然这里没有删除“跳页”功能,但这里可以看到一个有趣的现象,不管我们搜索什么内容,只要商品结 果足够多,返回的商品列表都是仅展示前100页的数据,我们不难发现,其实召回的商品被“截断”了,不 管你有多少,我都只允许你查询前100页,其实这本质和ES中的max_result_window作用是一样的,都 是限制你去搜索更深页数的数据。 手机端APP就更不用说了,直接是下拉加载更多,连分页条都没有,相当于你只能点击“下一页”。 那么回到当初的问题,我们牺牲了用户体验了吗? 不仅没有,而且用户体验大大提升了! 首先那些直接输入很大的页码,直接点击跳页的用户,本身就是恶意用户,阻止其行为是理所应 当,因此删除“ 跳页”,功能并无不妥!

d

Days

h

Hours

m

Minutes

s

Seconds

ms

Milliseconds

micros

Microseconds

nanos

Nanoseconds 其次,真正的通过搜索引擎来检索其意向数据的用户,只关心前几页数据,即便他通过分页条跳了 几页,但这种搜索并不涉及深度分页,即便它不停的点下去,我们也有其它方案解决此问题。 类似淘宝这种直接截断前100页数据的做法,看似暴力,其实是在不牺牲用户体验的前提下,极大 的提升了搜索的性能,这也变相的为哪些“正常用户”,提升了搜索体验,何乐不为?

官方已不推荐使用滚动查询进行深度分页查询,因为无法保存索引状态。

4.2.1 适合场景

单个[滚动搜索]请求中检索大量结果,即非“C端业务”场景

4.2.2 使用

时间单位: 为了使用滚动,初始搜索请求应该scroll 在查询字符串中指定参数,该参数告诉 Elasticsearch 应该保 持“搜索上下文”多长时间,例如?scroll=1m 。结果如下:

GET <index>/_search?scroll=1m
{
"size": 100
}
{
"_scroll_id" :
"DXF1ZXJ5QW5kRmV0Y2gBAAAAAAABVWsWN3Q4dDJjcVVRQ0NBbllGMmFqN0ZVZw==",
"took" : 0,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 21921750,
"relation" : "eq"

上述请求的结果包含一个_scroll_id,应将其传递给scrollAPI 以检索下一批结果。 滚动返回在初始搜索请求时与搜索匹配的所有文档。它会忽略对这些文档的任何后续更改。该scroll_id 标识一个搜索上下文它记录身边的一切Elasticsearch需要返回正确的文件。搜索上下文由初始请求创 建,并由后续请求保持活动状态。

4.2.3 注意

Scroll上下文的存活时间是滚动的,下次执行查询会刷新,也就是说,不需要足够长来处理所有数 据,它只需要足够长来处理前一批结果。保持旧段处于活动状态意味着需要更多的磁盘空间和文件 句柄。确保您已将节点配置为具有充足的空闲文件句柄。 为防止因打开过多Scrolls而导致的问题,不允许用户打开超过一定限制的Scrolls。默认情况下,打 开Scrolls的最大数量为 500。此限制可以通过search.max_open_scroll_context集群设置进行更新 。

4.2.4 清除滚动上下文

scroll 超过超时后,搜索上下文会自动删除。然而,保持Scrolls打开是有代价的,因此一旦不再使用 该clear-scroll API ,就应明确清除Scroll上下文

4.3 Search After

4.3.1 代码

},
"max_score" : 1.0,
"hits" : [
...
]
}
}

#清除单个

DELETE /_search/scroll
{
"scroll_id" :
"DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAAAD4WYm9laVYtZndUQlNsdDcwakFMNjU1QQ=="
}

#清除多个

DELETE /_search/scroll
{
"scroll_id" : [
"scroll_id1",
"scroll_id2"
]
}

#清除所有

DELETE /_search/scroll/_all
GET product/_search
{
"size": 2,
"query": {
"match_all": {}

4.3.2 如何使用 search after 解决大型搜索引擎场景下深度分页问题

不支持向前搜索,只能向后执行 每次只能向后搜索1页数据 适用于C端业务 以百度为例,默认加载10页数据,假设每页数据为 10条,其实对于ES而言,还不涉及深度分页问题, 因为只是查询了一百条数据。 如果项目并发请求并不高,可以单次查询 100 条数据。或者采用懒加载的方式异步请求前十页数据。后 面的数据采用 search after 向后翻页即可。

6、分片与副本作用

1. 分片:用来分布式存储、分散压力、提高写入 / 查询并发。

作用:

  1. 数据水平拆分:把一个大索引切分成多份,分散在不同节点,解决单节点存储上限问题。
  2. 提高写入并发:写入可以分散到多个主分片,提升写入吞吐量。
  3. 分布式计算:查询时多分片并行执行,加快查询速度。 特点: 索引创建时指定数量,后期不能修改 真正存储数据、承担写入的单元
},
"sort": [
{
"price": "desc",
"_id": "asc"
}
]
}
GET product/_search
{
"size": 2,
"query": {
"match_all": {}
},
"search_after": [
5999,
"6"
],
"sort": [
{
"price": "desc",
"_id": "asc"
}
]
}

2. 副本:用来做数据备份、保证高可用、分担查询流量。

作用:

  1. 高可用:主分片挂掉,副本可以提升为主分片,不丢数据、不停服务。
  2. 提高查询并发:查询请求可以落在主分片或副本分片,分摊读压力。 特点: 数量可以随时修改 不承担写入,只同步主分片数据

7、写数据流程

一句话总结:先写内存 buffer + 日志 translog →每秒 refresh 成 segment →定时 flush 到磁盘→

清空 translog

完整流程:

  1. 客户端请求→协调节点
  2. 路由分片:hash(id) % 主分片数找到对应主分片
  3. 写 buffer + translog 写入内存 buffer(此时数据不可搜索) 同时写translog日志(防止宕机丢数据)
  4. refresh 过程(默认 1 秒) buffer 数据生成segment写入文件系统缓存 数据可被搜索 buffer 清空
  5. flush 与 commit 满足条件后,segment 刷入磁盘 translog 清空,数据持久化完成

8、近实时原理

写入不是立刻可查,而是默认 1 秒 refresh 一次,把内存 buffer 生成 segment 进入文件系统缓存,

这时候才能被搜索到。

详细流程

  1. 数据先写入内存 buffer,同时写translog防止丢失。
  2. 此时数据不能被搜索。
  3. 默认每隔 1 秒,ES 执行一次 refresh: 将 buffer 中的数据生成segment写入文件系统缓存 数据从此可被搜索 清空 buffer
  4. 之后再通过 flush 真正落盘。

9、mapping 中 text 和 keyword 区别

text 会分词,用于全文搜索,不能聚合排序;

keyword 不分词,用于精确匹配、过滤、排序和聚合。

  1. text 类型 会分词:把文本切成一个个词,建立倒排索引。 支持全文检索:可以模糊搜索、匹配关键词。 不能排序、不能聚合(除非开 fielddata,不推荐)。 适用:文章内容、标题、描述、评论等需要搜索的长文本。
  2. keyword 类型 不分词:整体作为一个词存入索引。 精确匹配:用于 term 查询、过滤。 可以排序、可以聚合。 适用:状态值、枚举、标签、手机号、ID、英文名称、城市等不需要分词的字段。

10、term 和 match 区别

term 不分词,精确匹配,适合 keyword;

match 先分词再匹配,适合 text 全文检索。

  1. term 查询 不对查询内容分词,直接拿去倒排索引里查。 用于:精确匹配 适合:keyword、数字、ID、枚举 不适合:text 字段(因为存的时候分词了)
  2. match 查询 先对查询内容分词,再去匹配。 用于:全文检索 适合:text 字段 案例: 字段:“我喜欢Java” , text 类型分词后:我、喜欢、Java term:“喜欢 Java”→查不到(没有这个完整词) match:“喜欢 Java”→分词为喜欢、Java ,能查到

11、ES 写入优化

加大 refresh、关闭副本、批量写入、优化分片、用好 translog,性能直接起飞。

ES 写入优化主要从这几点入手:

  1. 写入前关闭副本(“number_of_replicas”: 0),调大 refresh_interval(“refresh_interval”: ”60s”);
  2. 使用 bulk 批量写入:不用单条写入,用_bulk,批量大小:5005000 条,总量515MB
  3. 合理设置分片数量,控制单分片大小,单个分片大小控制在20~50GB,主分片数 ≈ 总数据量 / 50GB;
  4. 优化 translog 刷盘策略,“translog.durability”: “async” “translog.sync_interval”: “5s”;
  5. 精简 mapping,关闭不必要字段索引(index: false );
  6. 使用 SSD,合理配置 JVM 内存(堆内存Xmx = Xms ≤ 32GB)。
  7. 段合并优化,ES 自动合并,可适当调高线程,避免大量小 segment 导致写入抖动

12、脑裂问题

脑裂是集群网络异常时出现多个 Master,导致数据不一致; 通过配置minimum_master_nodes = (主节点数/2)+1 ,保证只有超过半数节点才能选举主节点,即可 避免。 ES 7.x 以后已经去掉这个配置,内部自动解决脑裂,不用手动配置。 3 个主节点→设置 2 5 个主节点→设置 3

13、集群红黄状态

绿:全部正常,所有主分片 + 所有副本分片都正常分配;

黄:主分片正常、副本不正常,数据不丢,服务可用,但没有高可用;

红:至少一个主分片异常,数据不完整。

Yellow 常见原因

节点宕机,副本没地方分配 分片分配规则限制(分片感知、路由) 副本数设置比节点数多

Red 常见原因

节点宕机,主分片丢失 磁盘满、权限问题 数据损坏 集群脑裂 / 异常重启

七、微服务

1、分布式和微服务

1、分布式和微服务的区别是什么

分布式系统和微服务是两个相关但不同的概念,它们都涉及到软件架构中的组织和设计原则。以下是它 们的区别:

  1. 定义和范围: 分布式系统:分布式系统是由多台计算机或服务器协同工作,通过网络通信来完成共同的任 务。这些计算机可以分布在不同的地理位置,但它们通过网络连接进行协作。 微服务:微服务是一种软件架构风格,将一个大型应用程序拆分成一组小型、独立的服务。每 个微服务都专注于执行一个特定的业务功能,可以独立开发、部署和扩展。
  2. 关注点: 分布式系统:关注于如何将不同的计算机或服务器连接起来,以实现高性能、高可用性和负载 均衡等目标。 微服务:关注于如何将大型应用程序拆分成更小、更可管理的部分,并通过松耦合的方式来实 现更灵活的开发、部署和维护。
  3. 通信方式: 分布式系统:分布式系统中的组件之间需要进行网络通信,常见的通信方式包括远程过程调用 (RPC)、消息队列等。 微服务:微服务之间通常使用HTTP等协议进行通信,可以通过RESTful API或其他通信方式来 实现。
  4. 数据一致性: 分布式系统:在分布式系统中,确保数据一致性是一个挑战,需要考虑分布式事务、数据复制 等问题。 微服务:每个微服务可以拥有自己的数据存储,因此可以根据需求选择适当的数据库类型,并 更容易管理数据一致性。
  5. 部署和扩展: 分布式系统:需要关注整体系统的部署和扩展,可能需要考虑多台服务器的管理和配置。 微服务:每个微服务都可以独立部署和扩展,使得开发团队更容易管理和调整特定功能。 总的来说,分布式系统是一种基础架构模式,而微服务是一种架构风格。微服务通常可以在分布式系统 中实现,但并不是所有分布式系统都采用微服务架构。微服务架构的主要目标是使开发、部署和维护更 加灵活,适用于复杂的应用场景。

2、什么是微服务架构?优势?特点?

微服务架构是一种软件架构风格,将一个大型的应用程序拆分成多个小型、自治的服务单元,每个服务 单元都专注于执行特定的业务功能。每个微服务可以独立开发、部署、扩展和维护,通过轻量级通信机 制协同工作。微服务架构的优势和特点包括:

  1. 模块化与自治性:微服务架构通过拆分应用为多个服务单元,使得开发团队可以更加专注于各自的 业务领域。每个微服务都是独立的,有自己的代码、数据库、API等,可以在不影响其他服务的情 况下进行修改和更新。
  2. 灵活性与快速交付:微服务的自治性使得团队可以独立地开发、测试、部署和发布服务。这种独立 性加速了开发周期,使团队能够更快地交付新功能和更新。
  3. 可扩展性:微服务架构允许单独扩展每个服务,根据需求动态分配资源。这使得系统更具弹性,能 够应对高负载和流量峰值。
  4. 技术多样性:不同的微服务可以使用不同的技术栈,因为它们之间通过API通信。这允许团队选择 最适合其任务的技术,而不受整个应用的技术限制。
  5. 容错性与隔离性:由于微服务是自治的,一个服务的故障不会影响整个系统。故障在较小的范围内 隔离,从而提高了整体系统的容错性。
  6. 持续集成与持续交付:微服务架构有助于实现持续集成和持续交付,因为每个服务可以独立地构 建、测试和发布。这有助于减少部署的风险,并快速响应用户需求。
  7. 灵活的团队组织:微服务架构鼓励小团队负责特定的微服务,使得团队更加灵活,能够快速做出决 策和调整。 然而,微服务架构也带来了一些挑战,如分布式系统的复杂性、服务间通信的管理、数据一致性等问 题。对于不同的应用场景,需仔细权衡微服务的优势与挑战,以确定是否采用这种架构。

3、负载均衡算法、类型

1、轮询法 将请求按顺序轮流地分配到后端服务器上,它均衡地对待后端的每一台服务器,而不关心服务器实际的 连接数和当前的系统负载。 2、随机法 通过系统的随机算法,根据后端服务器的列表大小值来随机选取其中的一台服务器进行访问。由概率统 计理论可以得知,随着客户端调用服务端的次数增多,其实际效果越来越接近于平均分配调用量到后端 的每一台服务器,也就是轮询的结果。 3、源地址哈希法 源地址哈希的思想是根据获取客户端的IP地址,通过哈希函数计算得到的一个数值,用该数值对服务器 列表的大小进行取模运算,得到的结果便是客服端要访问服务器的序号。采用源地址哈希法进行负载均 衡,同一IP地址的客户端,当后端服务器列表不变时,它每次都会映射到同一台后端服务器进行访问。 4、加权轮询法 不同的后端服务器可能机器的配置和当前系统的负载并不相同,因此它们的抗压能力也不相同。给配置 高、负载低的机器配置更高的权重,让其处理更多的请;而配置低、负载高的机器,给其分配较低的权 重,降低其系统负载,加权轮询能很好地处理这一问题,并将请求顺序且按照权重分配到后端。 5、加权随机法 与加权轮询法一样,加权随机法也根据后端机器的配置,系统的负载分配不同的权重。不同的是,它是 按照权重随机请求后端服务器,而非顺序。 6、最小连接数法 最小连接数算法比较灵活和智能,由于后端服务器的配置不尽相同,对于请求的处理有快有慢,它是根 据后端服务器当前的连接情况,动态地选取其中当前积压连接数最少的一台服务器来处理当前的请求, 尽可能地提高后端服务的利用效率,将负责合理地分流到每一台服务器。

类型:

1、DNS 方式实现负载均衡 2、硬件负载均衡:F5 和 A10 3、软件负载均衡:Nginx 、 HAproxy 、 LVS 其中的区别: Nginx :七层负载均衡,支持 HTTP、E-mail 协议,同时也支持 4 层负载均衡; HAproxy :支持七层规则的,性能也很不错。OpenStack 默认使用的负载均衡软件就是 HAproxy; LVS :运行在内核态,性能是软件负载均衡中最高的,严格来说工作在三层,所以更通用一些, 适用各种应用服务。

4、分布式架构下,Session 共享有什么方案

采用无状态服务,抛弃Session,使用中间件存储,比如MySQL/Redis等。 把 Session 放到 Redis 中存储,虽然架构上变得复杂,并且需要多访问一次 Redis ,但是这种方案 带来的好处也是很大的。 存入cookie(要考虑跨域问题,且有安全风险) Tomcat集群Session同步 使用Spring-Session IP 绑定策略 实现了 Session 共享好处; 1、可以水平扩展(增加 Redis 服务器); 2、服务器重启 Session 不丢失(不过也要注意数据在 Redis 中的刷新/失效机制); 3、不仅可以跨服务器 Session 共享,甚至可以跨平台(例如网页端和 APP 端);

5、分布式Id生成方案

uuid 数据库自增序列 使用 Nginx (或其他复杂均衡软硬件)中的 IP 绑定策略,同一个 IP 只能在指定的同一个机器访 问,但是这样做失去了负载均衡的意义,当挂掉一台服务器的时候,会影响一批用户的使用,风险很大; 1:当前日期和时间时间戳 2:时钟序列。计数器 3:全局唯一的IEEE机器识别号,如果有网卡,从网卡MAC地址获得,没有网卡以其他方式获得。 优点:代码简单,性能好(本地生成,没有网络消耗),保证唯一(相对而言,重复概率极低可以忽 略) 缺点: 1)每次生成的ID都是无序的,而且不是全数字,且无法保证趋势递增。 2)UUID生成的是字符串,字符串存储性能差,查询效率慢,写的时候由于不能产生顺序的append 操作,在进行insert操作,可能会导致频繁的页分裂,这种操作在记录占用空间比较大的情况下,性 能下降比较大,还会增加读取磁盘次数。 3)UUID长度过长,不适用于存储,耗费数据库性能。 4)ID无一定业务含义,可读性差。 单机模式:

**优点:**

实现简单,依靠数据库即可,成本小。 ID数字化,单调自增,满足数据库存储和查询性能。 具有一定的业务可读性。(结合业务code)

**缺点:**

强依赖DB,存在单点问题,如果数据库宕机,则业务不可用。 DB生成ID性能有限,单点数据库压力大,无法扛高并发场景。 信息安全问题,比如暴露订单量,url查询改一下id查到别人的订单 分布式下生成的id会重复 **数据库高可用:**多主模式做负载,基于序列的起始值和步长设置,不同的初始值,相同的步长,步长大 于等于节点数。

**优点:**

解决了ID生成的单点问题,同时平衡了负载。 解决了分布式环境下id重复问题。

**缺点:**

基于Redis、mongodb、zk等中间件生成 雪花算法

6、如何实现接口的幂等性

1、token 机制

1、服务端提供了发送 token 的接口。我们在分析业务的时候,哪些业务是存在幂等问题的, 就必须在执行业务前,先去获取 token,服务器会把 token 保存到 Redis 中。 2、然后调用业务接口请求时,把 token 携带过去,一般放在请求头部。 3、服务器判断 token 是否存在 Redis 中,存在表示第一次请求,然后删除 token,继续执行业 务。 4、如果判断 token 不存在 Redis 中,就表示是重复操作,直接返回重复标记给 client,这样 就保证了业务代码,不被重复执行。

危险性:

1、先删除 token 还是后删除 token; (1) 先删除可能导致,业务确实没有执行,重试还带上之前 token,由于防重设计导致, 请求还是不能执行。 (2) 后删除可能导致,业务处理成功,但是服务闪断,出现超时,没有删除 token,别 人继续重试,导致业务被执行两边 (3) 我们最好设计为先删除 token,如果业务调用失败,就重新获取 token 再次请求。 2、Token 获取、比较和删除必须是原子性 (1) Redis.get(token) 、token.equals、Redis.del(token)如果这两个操作不是原子,可能导 致,高并发下,都 get 到同样的数据,判断都成功,继续业务并发执行 (2) 可以在 Redis 使用 lua 脚本完成这个操作。 系统扩容困难:系统定义好步长之后,增加机器之后调整步长困难。 主从同步的时候:电商下单->支付insert master db select数据,因为数据同步延迟导致 查不到这个数据。加cache(不是最好的解决方式)数据要求比较严谨的话查master主库。 生成一个64bit的整性数字 第一位符号位固定为0,41位时间戳,10位workId,12位序列号 位数可以有不同实现。

**优点:**

每个毫秒值包含的ID值很多,不够可以变动位数来增加,性能佳(依赖workId的实现)。 时间戳值在高位,中间是固定的机器码,自增的序列在低位,整个ID是趋势递增的。 能够根据业务场景数据库节点布置灵活挑战bit位划分,灵活度高。

**缺点:**

强依赖于机器时钟,如果时钟回拨,会导致重复的ID生成,所以一般基于此的算法发现时钟回拨, 都会抛异常处理,阻止ID生成,这可能导致服务不可用

ifRedis.call('get', KEYS[1]) ==ARGV[1] thenreturnRedis.call('del', KEYS[1])
elsereturn0end

2、各种锁机制

1、数据库悲观锁

2、数据库乐观锁 这种方法适合在更新的场景中, update t_goods set count = count -1 , version = version + 1 where good_id=2 and version = 1根据 version 版本,也就是在操作库存前先获取当前商品的 version 版本号,然后操作的时候带上此 version 号。我们梳理下,我们第一次操作库存时,得到 version 为 1,调用库存服务version 变成了 2;但返回 给订单服务出现了问题,订单服务又一次发起调用库存服务,当订 单服务传如的 version 还是 1,再执行上面的 sql 语句时,就不会执行;因为 version 已经变 为 2 了,where 条件就不成立。这样就保证了不管调用几次,只会真正的处理一次。 3、业务层分布式锁 如果多个机器可能在同一时间同时处理相同的数据,比如多台机器定时任务都拿到了相同数 据处理,我们就可以加分布式锁,锁定此数据,处理完成后释放锁。获取到锁的必须先判断 这个数据是否被处理过。

3、各种唯一约束

1、数据库唯一约束 插入数据,应该按照唯一索引进行插入,比如订单号,相同的订单就不可能有两条记录插入。 我们在数据库层面防止重复。 这个机制是利用了数据库的主键唯一约束的特性,解决了在 insert 场景时幂等问题。但主键 的要求不是自增的主键,这样就需要业务生成全局唯一的主键。 如果是分库分表场景下,路由规则要保证相同请求下,落地在同一个数据库和同一表中,要 不然数据库主键约束就不起效果了,因为是不同的数据库和表主键不相关。 2、Redis set 防重 很多数据需要处理,只能被处理一次,比如我们可以计算数据的 MD5 将其放入 Redis 的 set, 每次处理数据,先看这个 MD5 是否已经存在,存在就不处理。

4、防重表

使用订单号 orderNo 做为去重表的唯一索引,把唯一索引插入去重表,再进行业务操作,且 他们在同一个事务中。这个保证了重复请求时,因为去重表有唯一约束,导致请求失败,避 免了幂等问题。这里要注意的是,去重表和业务表应该在同一库中,这样就保证了在同一个 事务,即使业务操作失败了,也会把去重表的数据回滚。这个很好的保证了数据一致性。

7、实现一个分布式锁需要考虑哪些问题?

实现一个分布式锁时,需要考虑以下几个问题:

  1. 锁的唯一性:在分布式环境下,需要确保同一把锁在不同的节点之间是唯一的。可以使用全局唯一 标识符(例如基于ZooKeeper或Redis的分布式锁)来确保锁的唯一性。
  2. 死锁和活锁:分布式环境下的死锁和活锁是需要避免的问题。死锁是指多个节点互相等待对方释放 锁的情况,而活锁是指多个节点不断争夺锁资源,但没有一个节点能够成功获取锁。为了避免死锁 和活锁,可以使用超时机制、重试机制、随机等待时间等策略来解决。
  3. 锁的可重入性:分布式锁是否支持可重入是需要考虑的问题。可重入意味着同一个线程可以多次获 取同一把锁,而不会发生死锁。在实现分布式锁时,需要考虑是否支持可重入,并在实现时进行相 应的处理。
  4. 锁的粒度:锁的粒度是指锁的范围,即锁定的是整个系统、模块、方法还是更细粒度的资源。在分 布式环境下,锁的粒度需要根据实际需求进行选择,避免锁的范围大或过小。
  5. 锁的性能和可靠性:分布式锁的性能和可靠性是需要考虑的问题。锁的获取和释放需要保证高效且 可靠,同时要考虑网络延迟、节点故障等因素对锁性能和可靠性的影响。
  6. 锁的容错和容量:在分布式环境下,需要考虑节点故障和网络分区等异常情况对锁的影响。可以使 用多个节点进行容错和冗余,以确保锁的可用性和容量。 综上所述,实现一个分布式锁需要考虑锁的唯一性、死锁和活锁、锁的可重入性、锁的粒度、锁的性能 和可靠性,以及锁的容错和容量等问题。根据具体需求选择适合的分布式锁方案,并在实现时合理处理 这些问题。

8、分布式锁的解决方案

需要这个锁独立于每一个服务之外,而不是在服务里面。 数据库:利用主键冲突控制一次只有一个线程能获取锁,非阻塞、不可重入、单点、失效时间。 Zookeeper分布式锁: Redis分布式锁:setNX,单线程处理网络请求,不需要考虑并发安全性。 所有服务节点设置相同的key,返回为0、则锁获取失败 删除锁:判断线程唯一标志,再删除。 可重入性及锁续期没有实现,通过Redisson解决(类似AQS的实现,看门狗监听机制) redlock:意思的机制都只操作单节点、即使Redis通过sentinel保证高可用,如果这个master节点由于 某些原因发生了主从切换,那么就会出现锁丢失的情况(Redis同步设置可能数据丢失)。redlock从多 个节点申请锁,当一半以上节点获取成功、锁才算获取成功,Redission有相应的实现。

9、微服务架构的服务治理有哪些实现方案

微服务架构的服务治理有以下几种实现方案:

  1. 服务注册与发现:使用服务注册表来管理所有微服务的信息,包括服务名称、地址、版本等。服务 提供者将自己的信息注册到注册表中,服务消费者通过注册表来发现可用的服务。常见的服务注册 与发现工具有Consul、ZooKeeper和etcd等。
  2. 负载均衡:通过在服务提供者与服务消费者之间引入负载均衡器,将请求平均分配到多个服务实例 上,以提高系统的可伸缩性和可用性。常见的负载均衡器有Nginx、HAProxy和Envoy等。
  3. 健康检查与容错机制:通过定期的健康检查来监测服务的可用性和状态,并根据检查结果进行容错 处理,比如自动剔除不可用的服务实例。常见的健康检查工具有Spring Cloud的Spring Boot Admin、Netflix的Hystrix和Istio等。 zk通过临时节点,解决了死锁的问题,一旦客户端获取到锁之后突然挂掉(Session连接断开),那么这个临 时节点就会自动删除掉,其他客户端自动获取锁。临时顺序节点解决惊群效应
setnx

问题: 1、早期版本没有超时参数,需要单独设置,存在死锁问题(中途宕机)。 2、后期版本提供加锁与设置时间原子操作,但是存在任务超时,锁自动释放,导致并发问题,加锁与释 放锁不是同一线程问题。 4. 熔断器与降级:在高并发或异常情况下,通过熔断器来控制请求的流量,避免服务的雪崩效应。同 时可以通过降级策略,在服务不可用时返回默认值或者静态数据,保证系统的可用性。常见的熔断 器和降级工具有Netflix的Hystrix和Sentinel等。 5. 配置中心:将微服务的配置集中管理,实现配置的动态更新和版本控制。通过配置中心,可以动态 修改微服务的配置,而无需重启服务。常见的配置中心有Spring Cloud Config、Apollo和Consul 等。 6. API 网关:通过引入 API 网关来对外暴露微服务的统一接口,实现请求的路由、转发和过滤等功 能。API 网关可以对请求进行鉴权和限流,提供统一的访问控制和监控。常见的 API 网关有Spring Cloud Gateway、Netflix的Zuul和Kong等。 以上是常见的微服务架构的服务治理实现方案,可以根据实际需求选择适合的方案进行使用。

10、常见的限流算法有哪些

  1. (如图)计数器限流,一般用在单一维度的访问频率限制上,比如短信验证码每隔 60s 只能发送一 次,或者接口调用次数等 它的实现方法很简单,每调用一次就加 1,处理结束以后减一。
  2. (如图)滑动窗口限流,本质上也是一种计数器,只是通过以时间为维度的可滑动窗口设计,来减 少了临界值带来的并发超过阈值的问题。每次进行数据统计的时候,只需要统计这个窗口内每个时 间刻度的访问量就可以了。Spring Cloud 里面的熔断框架 Hystrix ,以及Spring Cloud Alibaba 里 面的Sentinel 都采用了滑动窗口来做数据统计。
  3. (如图)漏桶算法,它是一种恒定速率的限流算法,不管请求量是多少,服务端的处理效率是恒定 的。基于MQ 来实现的生产者消费者模型,其实算是一种漏桶限流算法。
  4. 令牌桶算法,相对漏桶算法来说,它可以处理突发流量的问题。它的核心思想是,令牌桶以恒定速 率去生成令牌保存到令牌桶里面,桶的大小是固定的,令牌桶满了以后就不再生成令牌。每个客户 端请求进来的时候,必须从令牌桶获得一个令牌才能访问,否则排队等待。在流量低峰的时候,令 牌桶会出现堆积,因此当出现瞬时高峰的时候,有足够多的令牌可以获取,因此令牌桶能够允许瞬 时流量的处理。

2、分布式事务

1、CAP理论,BASE理论

Consistency (一致性): 即更新操作成功并返回客户端后,所有节点在同一时间的数据完全一致。 对于客户端来说,一致性指的是并发访问时更新过的数据如何获取的问题。 从服务端来看,则是更新后如何复制分布到整个系统,以保证数据最终一致。 Availability (可用性): 即服务一直可用,而且是正常响应时间。系统能够很好的为用户服务,不出现用户操作失败或者访问超 时等用户体验不好的情况。 Partition Tolerance (分区容错性): 即分布式系统在遇到某节点或网络分区故障的时候,仍然能够对外提供满足一致性和可用性的服务。分 区容错性要求能够使应用虽然是一个分布式系统,而看上去却好像是在一个可以运转正常的整体。比如 现在的分布式系统中有某一个或者几个机器宕掉了,其他剩下的机器还能够正常运转满足系统需求,对 于用户而言并没有什么体验上的影响。 CP和AP:分区容错是必须保证的,当发生网络分区的时候,如果要继续服务,那么强一致性和可用性只 能 2 选 1。

BASE是Basically Available(基本可用)、Soft state(软状态)和Eventually consistent(最终一

致性)

BASE理论是对CAP中一致性和可用性权衡的结果,其来源于对大规模互联网系统分布式实践的总结,是 基于CAP定理逐步演化而来的。BASE理论的核心思想是:即使无法做到强一致性,但每个应用都可以根 据自身业务特点,采用适当的方式来使系统达到最终一致性。 基本可用: 响应时间上的损失: 正常情况下,处理用户请求需要 0.5s 返回结果,但是由于系统出现故障,处理 用户请求的时间变为 3 s。 系统功能上的损失:正常情况下,用户可以使用系统的全部功能,但是由于系统访问量突然剧增, 系统的部分非核心功能无法使用。 软状态:数据同步允许一定的延迟 最终一致性:系统中所有的数据副本,在经过一段时间的同步后,最终能够达到一个一致的状态,不要 求实时。

2、什么是分布式事务

分布式事务是指在分布式系统中涉及多个独立的数据库或服务的事务操作,它需要确保这些操作要么全 部成功执行,要么全部回滚,以保持数据的一致性和可靠性。分布式事务的目标是在不同的系统之间维 护数据的一致性,即使在出现故障或部分操作失败的情况下也能够保持数据的正确性。 在传统的单一数据库事务中,事务是由数据库管理系统负责管理和保障的,但在分布式环境下,涉及到 多个独立的数据库或服务,各自拥有独立的事务管理机制,因此需要特殊的方法来确保分布式事务的一 致性。 分布式事务面临的挑战和问题包括: 原子性(Atomicity):分布式事务需要确保涉及的所有操作要么都成功执行,要么都回滚,不能出现 部分操作成功而部分操作失败的情况。 一致性(Consistency):分布式事务需要保证各个参与方的数据保持一致性,即在事务开始和结束 时,系统的数据状态应该满足特定的约束。 隔离性(Isolation):分布式事务的操作应该与其他事务隔离,避免不同事务之间的干扰和冲突。 持久性(Durability):分布式事务需要确保在事务提交后,其结果被可靠地持久化,以防止数据丢 失。 为了实现分布式事务,有一些常见的方法和技术,包括两阶段提交(Two-Phase Commit,2PC)、三 阶段提交(Three-Phase Commit,3PC)、补偿事务(Compensating Transaction)等。这些方法在 不同的场景下有不同的适用性和权衡,开发人员需要根据系统的需求和复杂性选择适合的方法来处理分 布式事务。同时,分布式事务的实现也需要考虑到性能、可靠性和复杂性等方面的因素。

3、什么是TCC,和2PC有什么区别

TCC(Try-Confirm-Cancel)和2PC(Two-Phase Commit)都是用于处理分布式事务的协议,但它们在 处理方式和适用场景上存在一些区别。 TCC (Try-Confirm-Cancel): TCC是一种分布式事务处理方法,它将事务拆分为三个阶段:尝试(Try)、 确认(Confirm)和取消(Cancel)。每个阶段都有相应的操作。在尝试阶段,参与者尝试执行事务操 作,检查是否满足执行条件。如果所有参与者的尝试都成功,进入确认阶段,参与者确认执行事务。如 果任何一个参与者的尝试失败或确认阶段失败,将进入取消阶段,执行事务的补偿操作来恢复系统到一 致状态。TCC注重于事务的补偿机制,以确保数据的一致性。 2PC (Two-Phase Commit): 2PC是另一种分布式事务处理协议,它在全局协调器和多个参与者之间进行 通信。它有两个阶段:准备(Prepare)和提交(Commit)。在准备阶段,全局协调器将询问所有参与 者是否可以执行事务,参与者会回复“可以”或“不可以”。如果所有参与者都回复“可以”,则进入提交阶 段,在此阶段全局协调器通知所有参与者提交事务。如果任何一个参与者回复“不可以”,全局协调器会 通知所有参与者中止事务。2PC依赖于中心化的协调器,其缺点包括单点故障和阻塞的可能性。 区别: 处理方式: TCC采用尝试-确认-取消的模式,强调补偿机制,而2PC采用两阶段提交的方式,依赖全局协 调器来决定提交或中止事务。 灵活性: TCC相对更加灵活,因为它允许开发人员在尝试和确认阶段之间插入补偿逻辑。2PC较为严 格,需要参与者在准备阶段做出确定性的回应。 复杂性和性能: TCC通常比2PC更适用于高并发环境,因为它的操作相对较轻,而2PC的中心化协调可 能导致性能瓶颈。 可用性: TCC通常在局部事务级别上具有更好的可用性,因为每个参与者可以独立决定是否尝试事务。 在选择TCC还是2PC时,需要根据系统的需求、复杂性和可靠性等因素进行权衡。

4、分布式事务解决方案

XA规范:分布式事务规范,定义了分布式事务模型 四个角色:事务管理器(协调者TM)、资源管理器(参与者RM),应用程序AP,通信资源管理器CRM。 全局事务:一个横跨多个数据库的事务,要么全部提交、要么全部回滚JTA事务时java对XA规范的实现, 对应JDBC的单库事务 1、两阶段协议: 第一阶段( prepare ):每个参与者执行本地事务但不提交,进入 ready 状态,并通知协调者已经准备 就绪。 第二阶段( commit )当协调者确认每个参与者都 ready 后,通知参与者进行 commit 操作;如果有参 与者 fail ,则发送 rollback 命令,各参与者做回滚。 问题: 单点故障:一旦事务管理器出现故障,整个系统不可用(参与者都会阻塞住) 数据不一致:在阶段二,如果事务管理器只发送了部分 commit 消息,此时网络发生异常,那么 只有部分参与者接收到 commit 消息,也就是说只有部分参与者提交了事务,使得系统数据不一 致。 响应时间较长:参与者和协调者资源都被锁住,提交或者回滚之后才能释放 不确定性:当协事务管理器发送 commit 之后,并且此时只有一个参与者收到了 commit,那么当该参 与者与事务管理器同时宕机之后,重新选举的事务管理器无法确定该条消息是否提交成功。 2、三阶段协议:主要是针对两阶段的优化,解决了2PC单点故障的问题,但是性能问题和不一致问题仍 然没有根本解决。 引入了超时机制解决参与者阻塞的问题,超时后本地提交,2pc只有协调者有超时机制。 第一阶段:CanCommit阶段,协调者询问事务参与者,是否有能力完成此次事务。 如果都返回yes,则进入第二阶段: 有一个返回no或等待响应超时,则中断事务,并向所有参与者发送abort请求。 第二阶段:PreCommit阶段,此时协调者会向所有的参与者发送PreCommit请求,参与者收到后开始执 行事务操作。参与者执行完事务操作后(此时属于未提交事务的状态),就会向协调者反馈“Ack”表示我 已经准备好提交了,并等待协调者的下一步指令。 第三阶段:DoCommit阶段,在阶段二中如果所有的参与者节点都返回了Ack,那么协调者就会从“预提 交状态”转变为“提交状态”。然后向所有的参与者节点发送”doCommit”请求,参与者节点在收到提交请 求后就会各自执行事务提交操作,并向协调者节点反馈“Ack”消息,协调者收到所有参与者的Ack消息后 完成事务。相反,如果有一个参与者节点未完成PreCommit的反馈或者反馈超时,那么协调者都会向所 有的参与者节点发送abort请求,从而中断事务。 3、TCC(补偿事务):Try、Confirm、Cancel 针对每个操作,都要注册一个与其对应的确认和补偿(撤销)操作Try操作做业务检查及资源预留, Confirm做业务确认操作,Cancel实现一个与Try相反的操作既回滚操作。TM首先发起所有的分支事务 的try操作,任何一个分支事务的try操作执行失败,TM将会发起所有 分支事务的Cancel操作,若try操作全部成功,TM将会发起所有分支事务的Confirm操作,其中 Confirm/Cancel操作若执行失败,TM会进行重试。 TCC模型对业务的侵入性较强,改造的难度较大,每个操作都需要有 try 、 confirm 、 cancel 三个接口 实现。 confirm 和 cancel 接口还必须实现幂等性。

4、消息队列的事务消息:

发送prepare消息到消息中间件 发送成功后,执行本地事务 如果事务执行成功,则commit,消息中间件将消息下发至消费端(commit前,消息不会被 消费) 如果事务执行失败,则回滚,消息中间件将这条prepare消息删除 消费端接收到消息进行消费,如果消费失败,则不断重试。 需要注意的是,每种分布式事务处理方法都有其适用的场景和权衡。选择适当的方法取决于系统的要 求、复杂性和可用技术栈。

5、什么是柔性事务

柔性事务(Flexible Transactions)是一种用于分布式系统中的事务处理模式,旨在处理跨多个参与者 的分布式事务。在分布式环境中,事务涉及到多个资源或服务,需要保证数据的一致性和可靠性。柔性 事务是为了在分布式系统中实现事务处理的灵活性和可扩展性而设计的。 柔性事务模式通常允许开发人员在事务的各个阶段插入逻辑,以便在出现错误或异常情况时进行处理。 这种模式的核心思想是,将事务处理拆分为三个阶段:尝试(Try)、确认(Confirm)和取消 (Cancel)。 尝试(Try):在这个阶段,事务的参与者尝试执行操作,并在操作完成之前不对外部资源产生影响。 这是一个”预提交”的阶段,用于检查所有资源是否可用,并为后续的确认阶段做准备。 确认(Confirm):如果所有参与者的尝试阶段都成功,那么系统会进入确认阶段。在这个阶段,参与 者提交事务,并将操作的结果应用到资源中。如果在这个阶段发生了错误,可以触发补偿逻辑,回滚之 前的操作,以确保数据的一致性。 取消(Cancel):如果在确认阶段发生错误,系统会进入取消阶段。在这个阶段,参与者会执行回滚操 作,将之前尝试阶段所做的更改撤销,以保持数据的一致性。 柔性事务相对于传统的两阶段提交(2PC)来说,具有更高的灵活性和可用性。它允许在不同的阶段插 入补偿逻辑,以应对各种异常情况,同时减少了全局协调器的依赖,从而降低了性能瓶颈的风险。这使 得柔性事务在高并发和复杂的分布式系统中更具优势。

6、谈谈你对 Seata 的理解

  1. 在微服务架构下,由于数据库和应用服务的拆分,导致原本一个事务单元中的多个 DML 操作,变 成了跨进程或者跨数据库的多个事务单元的多个 DML 操作, 而传统的数据库事务无法解决这类的问题,所以就引出了分布式事务的概念。
  2. 分布式事务本质上要解决的就是跨网络节点的多个事务的数据一致性问题,业内常见的解决方法有 两种 a. 强一致性,就是所有的事务参与者要么全部成功,要么全部失败,全局事务协调者需要知道每 个事务参与者的执行状态,再根据状态来决定数据的提交或者回滚! b. 最终一致性,也叫弱一致性,也就是多个网络节点的数据允许出现不一致的情况,但是在最终 的某个时间点会达成数据一致。 基于CAP 定理我们可以知道,强一致性方案对于应用的性能和可用性会有影响,所以对于数据一致性要 求不高的场景,就会采用最终一致性算法。
  3. 在分布式事务的实现上,对于强一致性,我们可以通过基于XA 协议下的二阶段提交来实现,对于 弱一致性,可以基于 TCC 事务模型、可靠性消息模型等方案来实现。
  4. 市面上有很多针对这些理论模型实现的分布式事务框架,我们可以在应用中集成这些框架来实现分 布式事务。 而Seata 就是其中一种,它是阿里开源的分布式事务解决方案,提供了高性能且简单易用的分布式事务 服务。

7、什么是Seata?他有哪几种模式

Seata是由阿里巴巴开源的一款分布式事务解决方案,旨在解决分布式系统中的数据一致性问题。Seata 提供了一套事务管理的解决方案,支持对分布式事务进行管理和协调。 Seata有以下四种常见的模式:

  1. AT 模式:这是一种无侵入的分布式事务解决方案。用户只需关注自己的业务 SQL,Seata 框架会 自动生成事务的二阶段提交和回滚操作。在一阶段,Seata 会拦截业务 SQL,解析 SQL 语义,找到 要更新的业务数据,并保存快照数据和行锁。二阶段如果是提交,Seata 只需清理数据;如果是回 滚,则用快照数据还原业务数据。
  2. TCC 模式:TCC(Try-Confirm-Cancel)模式需要用户根据自己的业务场景实现 Try、Confirm 和 Cancel 三个操作。Try 阶段是资源的检测和预留;Confirm 阶段执行业务操作提交;Cancel 阶段 是预留资源释放。
  3. Saga 模式:Saga 模式适用于长事务,由事件驱动,各个参与者之间异步执行。如果任何一个正向 操作执行失败,Saga 模式会执行前面各参与者的逆向回滚操作,回滚已提交的参与者,使分布式 事务回到初始状态。
  4. XA 模式:XA 模式是 Seata 支持的另一种分布式事务解决方案,它利用事务资源对 XA 协议的支 持,以 XA 协议的机制来管理分支事务。XA 模式是两阶段提交协议,通过资源管理器和事务协调者 来保证事务的原子性。

7、如何基于本地消息表实现分布式事务

当基于本地消息表实现分布式事务时,通常会采用一种称为“基于消息的最终一致性”或“最终一致性补偿” 模式。这个模式可以帮助在分布式环境中处理事务性操作,同时保持数据的一致性。 以下是基于本地消息表实现分布式事务的一般步骤: 操作记录与消息发送:当一个分布式事务发起时,主节点(或服务)将事务操作记录到本地数据库,并 将一个消息发送到消息队列中。这个消息包含了执行的操作、事务标识以及其他必要的信息。 消息传递与处理:其他参与者节点在收到消息后,根据消息中的操作类型执行相应的本地操作。这些本 地操作可能会修改各个节点的数据状态,但在这个阶段不会立即确认事务的完成。 确认阶段:在所有参与者节点执行完本地操作后,各节点会将一个确认消息发送回主节点。主节点根据 收到的确认消息来判断是否可以提交事务。如果有任何错误发生,主节点可以触发补偿逻辑。 补偿阶段:如果在确认阶段发生错误,主节点可以通过发送补偿消息来触发参与者节点执行相反的操 作,以撤销之前的更改,从而保持数据的一致性。这就是所谓的“最终一致性补偿”。 完成或取消:一旦确认阶段中的所有参与者节点都成功确认,事务被视为成功完成。如果在确认阶段有 任何错误,主节点会触发取消操作,参与者节点会执行补偿操作,将数据状态还原到事务开始前的状 态。 基于本地消息表的分布式事务实现主要依赖于消息队列的可靠性和分布式事务的补偿能力。这种模式可 以提高分布式系统的可用性和容错性,但同时也需要在应用程序中实现正确的补偿逻辑来处理可能的异 常情况。

3、Nacos

1、注册中心如何选型

选择注册中心时,需要考虑以下几个方面的因素:

  1. 高可用性:注册中心是服务治理的核心组件,因此高可用性是非常重要的。注册中心应该能够容忍 单点故障,并且能够自动进行故障转移和恢复。
  2. 性能和扩展性:注册中心需要能够处理大量的服务实例注册和查询请求,并保持较低的延迟。此 外,注册中心还应该支持水平扩展,以应对日益增长的服务规模。
  3. 数据一致性 (CP):注册中心应该保证数据的一致性,即使在面对网络分区或节点故障的情况下也能 保持数据的可靠性和准确性。
  4. 安全性:注册中心应该提供合适的安全机制,包括身份认证、访问控制和数据加密等,以保护服务 实例的注册信息和通信数据的安全。
  5. 功能和灵活性:注册中心应该提供丰富的功能,如服务发现、负载均衡、服务路由、健康检查等, 还应该具备灵活的配置和扩展能力,以满足不同的业务需求。
  6. 社区支持和生态系统:选择一个有活跃的社区和丰富的生态系统的注册中心,可以获得更好的技术 支持和资源,以及更多的集成和扩展选项。 常见的注册中心选型包括:ZooKeeper、Consul、Etcd、Eureka等。每个注册中心都有其特点和优势, 需要根据具体的业务需求和技术栈来选择适合的注册中心。同时,也可以考虑多个注册中心组合使用, 以实现高可用性和灵活性的要求。

2、什么是Nacos,主要用来作什么

Nacos是一个开源的分布式服务注册和配置中心。它提供了服务注册、发现、配置和管理的能力,可以 帮助开发者构建和管理微服务架构。 主要功能包括:

  1. 服务注册与发现:Nacos充当了服务注册中心的角色,服务提供者通过向Nacos注册自己的服务, 使得服务消费者能够方便地发现和调用服务。
  2. 动态配置管理:Nacos提供了统一的配置管理功能,可以集中管理应用程序的配置信息。它支持动 态刷新配置,可以在运行时动态修改应用程序的配置,而无需重启应用。
  3. 服务路由与负载均衡:Nacos可以根据服务的健康状况和负载情况,动态地进行服务路由和负载均 衡,以提供更好的服务质量和可用性。权重0.1,0.9
  4. 服务共享与版本管理:Nacos支持多租户的服务共享,不同的租户可以共享同一个服务。同时, Nacos还提供了版本管理功能,可以管理不同版本的服务。
  5. 服务监控与治理:Nacos提供了服务的健康检查和监控功能,可以实时监控服务的状态和性能指 标。此外,Nacos还提供了服务熔断、限流等治理能力,以提高系统的稳定性和可靠性。 总之,Nacos是一个功能强大的分布式服务注册和配置中心,它可以帮助开发者简化微服务架构的开发 和管理工作,提高系统的弹性和可伸缩性。

3、Nacos是AP的还是CP的

Nacos 同时支持 AP(可用性优先)和 CP(一致性优先)模式,具体取决于功能模块和配置:服务注册 与发现默认采用 AP 模式,而配置管理可切换为 CP 模式。 Nacos 的 AP 与 CP 模式设计‌ Nacos 作为分布式系统,基于 CAP 理论灵活适配不同场景需求,其核心设计如下:

  1. 服务注册与发现(默认 AP 模式)‌。 协议‌:采用自研的 Distro 协议,优先保证高可用性。‌ 特点‌:允许短暂数据不一致(最终一致性),节点故障时仍可读写,适合服务发现场景。‌ 局限性‌:无全局开关强制切换为 CP 模式。‌
  2. 配置管理(可选 CP 模式)‌。 协议‌:Nacos 2.x 引入 Raft/JRaft 协议实现强一致性。‌
配置方式‌:通过修改cluster.conf或 API 设置dataConsistency: raft启用 CP 模式。‌

适用场景‌:金融、支付等对配置一致性要求高的业务。‌

版本差异与实现机制

Nacos 1.x‌:仅支持 AP 模式,依赖 Derby/MySQL 存储。‌ Nacos 2.x‌:通过多协议分层设计,支持混合模式(临时实例用 Distro/AP,永久实例用 Raft/CP)。‌

4、Nacos如何实现的配置变化客户端可以感知到

Nacos通过以下机制实现配置变化客户端可以感知到:

  1. 长轮询(Long Polling):客户端可以向Nacos发送一个长轮询请求,如果配置发生变化,Nacos 会立即返回配置变化的通知给客户端。客户端在收到通知后可以及时更新本地配置。
  2. 客户端缓存:Nacos客户端会在本地缓存配置信息,定期从Nacos服务器拉取最新的配置。当 Nacos服务器上的配置发生变化时,客户端会通过定时任务或者其他方式从Nacos服务器获取最新 的配置。
  3. 事件通知机制:Nacos会将配置变更事件发布给订阅了该配置的客户端。客户端可以订阅特定的配 置,当该配置发生变化时,Nacos会发送事件通知给客户端,客户端收到通知后可以进行相应的处 理。 通过以上机制,Nacos实现了配置变化的实时感知。客户端可以根据自身的需求选择适合的方式来获取 配置变化的通知,并进行相应的处理。这样,当配置发生变化时,客户端可以及时更新配置,实现配置 的动态管理。

5、Nacos能同时实现AP和CP的原理是什么

Nacos能够同时实现AP(可用性和分区容忍性)和CP(一致性和分区容忍性)的原理是通过引入不同的 组件和机制来实现的。 在Nacos 2.x中,服务注册和发现模块采用了AP模型,通过将服务实例的注册信息存储在多个节点上, 实现了服务的高可用性和分区容忍性。当某个节点发生故障或网络分区时,其他节点仍然可以提供服务 注册和发现的功能。 而配置管理模块则采用了CP模型,通过使用Raft协议来实现一致性。当客户端更新配置时,Nacos会将 配置写入多个节点的Raft日志中,经过多数节点的确认后才会认为配置写入成功。这保证了配置的一致 性。在配置变更时,通过Raft协议的复制机制,确保配置变更在集群中的所有节点上同步,保证了配置 的一致性和可靠性。 通过这种方式,Nacos在不同的模块中根据需求选择了合适的一致性和可用性模型,从而同时实现了AP 和CP的特性。这使得Nacos能够满足分布式系统在服务注册、发现和配置管理等方面的需求,提供高可 用性、弹性和可靠性的服务。

6、Nacos服务发现的原理

服务发现: 调用方在第一去发起远程调用的时候,会把远程的服务以及该服务的实例都缓存到本地Map中,当后面 在去发起远程调用的时候,就会优先查询本地Map,如果本地Map有,那么直接根据本地Map的数据,去 发起远程调用,如若没有,才像远程(Nacos)发送一个请求:/nacos/v1/ns/list.获取到远程数据在同步 到缓存。 有了本地缓存机制,就可以保证我未来发起远程调用的时候,更快。但是会带来数据一致性问题。 如何解决本地缓存和远程数据一致性问题: 1、被动定时拉取,每隔10s钟会发起一个查询(远程Nacos)最新数据,来同步本地Map这样做虽然能 够解决最终这两份数据是一致的,但是在这一次定时任务和下一次定时任务开始之间,远程注册表发生 了变化,那么就获取不到远程(Nacos)最新。而对于定时任务发请求去要远程数据这个环节走的是tcp 协议,而用了 tcp协议之后就可以保证数据包不丢失。但是tcp协议会有三次握手和四次挥手,因此从效率来说,会慢 一些。 初始化1s执行这个定时任务,如果在某一次找远程要的时候没有出现异常,那么这个任务每隔10s会继 续执行,但是如果在找远程要数据的时候,出现了异常,那么这个时间就会从2s开始,最多到1min钟结 束。 2、主动推送更新,一旦远程服务注册表发生了改变,那么Nacos服务端会主动发送一个事件,并且使用 的是udp协议来发送的,接着我客户端只要订阅了这个udp的端口,那么就会让客户端主动去找远程 Nacos获取最新的注册表。upd协议:快,(因为没有三次握手和四种挥手的过程)但是可能会丢失数 据包。 最终Ncaos来解决服务发现数据不一致问题是通过TCP协议的定时任务发请求,和UDP协议的主动发送 事件请求,来解决一致性的。

7、Nacos注册中心原理

首先,服务注册的功能体现在: 服务实例启动时注册到服务注册表、关闭时则注销(服务注册)。 服务消费者可以通过查询服务注册表来获得可用的实例(服务发现)。 服务注册中心需要调用服务实例的健康检查API来验证其是否可以正确的处理请求(健康检查)。 Nacos服务注册和发现的实现原理的图如下:

  1. 服务(项目)启动时,根据spring-cloud-commons中spring.factories的配置,自动装配了类 AutoServiceRegistrationAutoConfiguration。
  2. AutoServiceRegistrationAutoConfiguration类中注入了类AutoServiceRegistration,其最终实现 子类实现了Spring的监听器。
  3. 根据监听器,执行了服务注册方法。而这个服务注册方法则是调用了NacosServiceRegistry的 register()方法。
  4. 该方法主要调用的是Nacos Client SDK中的NamingService下的registerInstance()方法完成服 务的注册。
  5. registerInstance()方法主要做两件事:服务实例的健康监测和实例的注册。
  6. 通过schedule()方法定时的发送数据包,检测实例的健康。
  7. 若健康监测通过,调用registerService()方法,通过OpenAPI方式执行服务注册,其中将实例 Instance的相关信息存储到HashMap中。

4、OpenFeign

1、什么是 OpenFeign

一个声明式、模板化的 HTTP 客户端,让微服务之间调用像调用本地方法一样简单,底层自动封装 HTTP 请求。

2、Feign 和 OpenFeign 区别

Feign:Netflix 原生,轻量 HTTP 客户端 OpenFeign:Spring 在 Feign 基础上封装增强,支持 Spring MVC 注解、整合 Ribbon、 Sentinel、Eureka/Nacos 等

生产都用 OpenFeign。

3、OpenFeign 核心作用

简化微服务 HTTP 调用 整合 Ribbon 实现负载均衡 整合 Sentinel 实现熔断降级 支持日志、重试、超时、拦截器

4、OpenFeign 执行流程

  1. 启动时扫描@FeignClient接口,生成动态代理
  2. 方法调用时,通过代理构造请求
  3. 整合 Ribbon 选择服务实例
  4. 编码、发送 HTTP 请求
  5. 解码响应返回结果

5、OpenFeign 支持哪些注解

和 Spring MVC 基本一致:

@GetMapping
@PostMapping
@RequestBody
@RequestParam
@PathVariable

6、OpenFeign 怎么实现负载均衡

默认整合 Ribbon,从注册中心获取服务列表,自动负载均衡。

7、OpenFeign 超时怎么配置

两种方式:

  1. 全局超时
  2. 针对单个服务配置
ribbon:
ConnectTimeout: 1000
ReadTimeout: 1000

服务名:

ribbon:
ConnectTimeout: 2000
ReadTimeout: 2000

8、OpenFeign 日志级别

NONE:无日志(默认) BASIC:请求方法、URL、状态码 HEADERS:请求 + 响应头 FULL:全部(头、体、线) 生产一般用 BASIC,排查用 FULL。

9、OpenFeign 支持异步吗

原生 OpenFeign 是同步的。

要异步:

使用CompletableFuture包装

或用 Spring Cloud 异步增强 或用 WebClient

10、OpenFeign 重试机制

默认通过Ribbon 重试: MaxAutoRetries MaxAutoRetriesNextServer 可关闭:

11、GET 请求传对象会变成 POST,为什么

因为 Feign 对复杂对象默认用@RequestBody ,变成 POST。 解决:

拆成单独@RequestParam
加@SpringQueryMap注解

12、OpenFeign 怎么实现熔断降级

整合Sentinel或Spring Cloud CircuitBreaker。 步骤:

  1. 开启 sentinel
  2. 写 Fallback 类
ribbon:
MaxAutoRetries: 0
MaxAutoRetriesNextServer: 0
3. @FeignClient(fallback = xxx.class)

13、OpenFeign 怎么传递 Token / 请求头

三种方式:

1. @RequestHeader手动传
  1. 实现RequestInterceptor拦截器自动传递
  2. 用 Feign 扩展配置

14、OpenFeign 核心组件

Feign.Builder:构造器 Client:实际发送请求 Encoder/Decoder:编解码 Logger/Logger.Level:日志 Contract:注解解析器 RequestInterceptor:请求拦截器

15、OpenFeign 底层用什么发送 HTTP

默认:HttpURLConnection 可替换: Apache HttpClient OkHttp 生产推荐OkHttp。

16、OpenFeign 和 RestTemplate 区别

RestTemplate:模板式,手写 URL、参数 OpenFeign:声明式,注解驱动,代码更简洁、易维护

5、Dubbo

1、RPC

什么是远程调用?

有的小伙伴误以为,远程调用,是指跨域物理距离的远。实际上,远程调用是指跨进程的功能调用,跨 进程可以理解成一个计算机节点的多个进程,或者多个计算机节点的多个进程。有的小伙伴误认为,远 程就是距离远。其实,远程并不是指距离上的远程,而是指由于进程和进程之间跨越进程的

什么是 RPC?

全称为Remote Procedure Call,翻译过来就是远程过程调用,它是一种通过网络从远程计算机程序上 请求服务,而不需要了解底层网络技术的协议,凡是符合该协议的框架,我们都可以称它为 RPC 框架。 关于RPC 协议,通俗来讲就是,A 计算机提供一个服务,B 计算机可以像调用本地服务那样调用 A 计算 机的服务。 要实现RPC,需要通过网络传输数据,并对调用的过程进行封装。现在比较流行的 RPC 框架,都会采用 TCP 作为底层传输协议。 RPC 强调的是过程调用,调用的过程对用户而言是透明的,用户不需要关心调用的细节,可以像调用本 地服务一样调用远程服务。 如图所示,一个完整的RPC 架构里面包含了四个核心的组件,分别是Client ,Server,Client Stub 以及 Server Stub: 1)客户端(Client),服务的调用方。 2)服务端(Server),真正的服务提供者。 3)客户端存根(Client Stub),存放服务端的地址消息,再将客户端的请求参数打包成网络消息,然 后通过网络远程发送给服务方。 4)服务端存根(Server Stub),接收客户端发送过来的消息,将消息解包,并调用本地的方法。目前 比较流行的开源 RPC 框架有Google 的gRPC,Facebook 的Thrift,阿里巴巴的Dubbo。

RPC 的运用场景和优势

在开发电商系统时,随着业务越来越复杂,走分布式架构这个方向是绝大部分互联网企业必然的选择。 分布式架构落地的里程碑,应该是阿里的去 IOE 运动的成功,它让互联网企业看到了,如何利用更少的 软硬件成本来支撑海量的用户。 分布式架构的核心,就是利用多个普通计算机节点,组成一个庞大而复杂的计算网络,提供高性能以及 高并发的能力支撑。 在分布式架构中,原本的单体应用被拆分成多个独立部署的服务分布在计算机网络上,这些服务必然需 要通过网络进行数据交互。 而RPC 框架就是解决在分布式架构中的各个业务服务彼此的网络通信问题。 一般来说,RPC 服务主要是针对大型的企业,也就说当我们业务复杂度以及用户量都比较高时,需要解 耦服务,扩展性强、部署灵活 一般市面上开源的 PRC 框架,除了提供基础的远程通信功能以外,还会在性能消耗、传输效率、服务治 理等方面做很多的设计,比如阿里开源的 RPC 框架Dubbo

2、Dubbo 核心组件与调用流程

核心组件: ① Provider:服务提供者 ② Consumer:服务消费者 ③ Registry:注册中心 ④ Monitor:监控 ⑤ Container:服务容器 调用流程: ① Provider 启动,向注册中心注册服务 ② Consumer 启动,订阅服务 ③注册中心推送 Provider 地址 ④ Consumer 本地负载均衡选一台调用 ⑤调用失败走容错机制 ⑥统计数据上报 Monitor

3、Dubbo 协议 vs 其他协议

dubbo、http、rmi、hessian、thrift、webservice。 默认 dubbo 协议,Netty 长连接,性能最高。

4、注册中心为什么用 ZK

Dubbo支持 ZK、Nacos、Redis。 ZK 用临时节点 + 监听,服务掉线自动删除,实时推送变更,稳定可靠。

5、负载均衡策略

Random 随机(默认) RoundRobin 轮询 LeastActive 最少活跃 ConsistentHash 一致性哈希

6、集群容错策略

Failover 失败自动切换重试(默认,读接口) Failfast 快速失败(写接口) Failsafe 安全失败,忽略异常 Failback 后台重试 Forking 并行调用多个 Broadcast 广播调用

7、服务降级、熔断理解

熔断:调用连续失败,暂时切断,防止雪崩 降级:压力大时,关闭非核心服务,返回兜底数据

8、Dubbo SPI 与 Java SPI 区别

Dubbo SPI 是 Dubbo 自己实现的扩展点加载机制,用来动态加载各种组件实现。 和 Java SPI 的区别

  1. Java SPI:一次性加载所有实现,浪费资源
  2. Java SPI:不能按名字获取,出错难定位
  3. Dubbo SPI:按需加载、按名字获取、支持 IOC/AOP,性能更强

9、序列化选型

Hessian2(默认):兼容性最好、最稳定,但性能一般 Kryo / FST:速度最快、体积最小,但兼容性稍差 JSON:可读性最好,但最慢、最占带宽

10、服务暴露流程

  1. 服务启动:服务提供者启动,Spring 加载配置,扫描@Service注解。
  2. 生成代理 & 封装 Invoker:将真实实现类封装成Invoker(Dubbo 统一调用模型)。
  3. 协议转换:将 Invoker 转换成Exporter,暴露服务。
  4. 创建 Server:启动网络服务器(Netty),监听配置端口。
  5. 注册到注册中心:将服务地址、接口、版本、协议等信息注册到Registry (ZooKeeper/Nacos)。
  6. 服务暴露完成:消费者可以从注册中心发现并调用该服务。

11、泛化调用使用场景

不依赖 API 包,只用接口名、方法名、参数就能调用,用于网关、测试平台。

典型使用场景:

  1. 网关统一调用 网关不依赖各个微服务 API,通过泛化统一转发请求。
  2. 测试平台 / 自动化测试 测试后台不需要引入依赖,直接输入接口名、方法名、参数调用。
  3. 低代码 / 配置化平台 通过配置就能调用服务,不用编译代码。
  4. 跨语言调用、第三方对接 不提供 API 包,只给接口信息,通过泛化调用。

核心特点:

无接口依赖

动态调用

常用于网关、测试平台、配置中心

12、异步调用实现

Dubbo 异步调用 = 客户端不阻塞,通过 CompletableFuture 或 RpcContext 获取结果,实现非阻塞调 用。 三种常用实现方式:

①使用CompletableFuture(推荐,最常用)
  1. 接口方法返回值改为:
  2. 提供者返回:
  3. 消费者调用: ②通过 XML / 注解配置异步
CompletableFuture<String>hello(Stringname);
returnCompletableFuture.completedFuture("hello");
CompletableFuture<String>future=service.hello("abc");
future.whenComplete((result, ex) -> {
// 处理结果
});
@Reference(async=true)
HelloServiceservice;

调用: ③基于 Callback 回调

  1. 定义回调接口:
  2. 调用时传入回调:

13、服务挂了怎么办

服务挂了,注册中心自动剔除,消费者不再调用;Dubbo 自带集群容错、失败重试、负载均衡,配合熔 断降级,保证整体可用。

  1. 注册中心层面 服务宕机后,注册中心(ZooKeeper/Nacos)会自动摘除节点 消费者会实时感知,不再调用已下线的服务
  2. 集群容错(Dubbo 自带)默认:failover 失败自动切换 调用失败→自动重试其他可用节点 避免单机故障影响整个调用 其他容错策略 failfast:快速失败,适合非幂等操作 failsafe:安全失败,忽略异常,适合日志 failback:失败自动恢复,后台重试 forking:并行调用多个,一个成功即返回
  3. 负载均衡 自动跳过异常节点 只把流量分给健康服务
  4. 限流、降级、熔断 熔断:连续失败达到阈值,打开熔断,暂时屏蔽调用 降级:非核心服务不可用时,返回兜底数据 / 默认值 限流:防止把压力打给下游已故障的服务
  5. 高可用架构保证
Stringresult=service.hello("test"); // 立即返回 null
// 获取异步 Future
CompletableFuture<String>future=
RpcContext.getContext().getCompletableFuture();
future.whenComplete(...);
publicinterfaceAsyncCallback {
voidonSuccess(Stringresult);
voidonException(Throwableex);
}
service.hello("test", newAsyncCallback() { ... });

集群部署,避免单点 监控告警(Prometheus/Grafana) 服务健康检查 自动扩缩容

14、Dubbo 调用时,Provider 执行很慢,会怎么样?怎么避免拖垮

整个系统?

  1. 如果 Provider 执行很慢,业务线程会被占住,导致新请求处理不过来。
  2. 消费者这边会超时、报错、重试,可能把 Provider 压垮。
  3. 为了避免拖垮系统,Dubbo 用了独立线程模型: IO 线程(Netty)只负责网络读写,不执行业务 业务线程池专门处理逻辑
  4. 再配合超时时间、重试次数、熔断、降级,就不会被慢接口拖垮整个系统。

15、Dubbo 如何实现高可用?

注册中心动态发现 + 多节点部署 + 负载均衡 + 集群容错 + 熔断降级。

  1. 注册中心动态感知 服务上下线实时通知消费者 宕机节点自动剔除,保证调用的是健康实例
  2. 集群容错机制 Failover:失败自动切换,重试其他节点(默认) Failfast:快速失败,不重试 Failsafe:出现异常直接忽略,不影响主线 Failback:失败后台定时重试 Forking:并行调用多个,一个成功就返回
  3. 负载均衡 避免单点压力过大 支持:随机、轮询、最少活跃、一致性哈希
  4. 熔断、降级、限流 熔断:连续失败达到阈值,暂时屏蔽异常服务 降级:非核心接口不可用时,返回兜底数据 限流:保护服务不被流量打崩
  5. 超时与重试 设置合理timeout,防止长时间阻塞 合理retries,避免雪崩,但要保证接口幂等
  6. 无状态设计 服务无状态,可随意水平扩容 部署多实例,杜绝单点故障
  7. 优雅停机 & 优雅上线 停机前停止接收新请求,处理完现有请求再下线 新服务启动预热完成再对外提供

16、介绍 Dubbo 的优势

Dubbo 是一款很成熟、高性能的 Java RPC 框架,它最大的优势就是让分布式服务调用变得简单、稳 定、好维护。 第一,调用像本地方法一样方便,不用自己写网络、编解码、TCP 通信,框架全帮你封装好了。 第二,自带服务注册发现,服务上下线自动感知,不用手动改配置。 第三,负载均衡、集群容错、重试、降级、熔断全都内置,不用自己重复造轮子,能有效防止服务 雪崩。 第四,扩展性极强,协议、注册中心、序列化、负载均衡策略都能通过 SPI 灵活替换。 第五,性能很高,默认用 Netty 长连接,序列化效率也高,高并发场景很稳。 最后,生态成熟、文档全、公司用得多,线上问题好排查,适合做微服务架构。

6、Sentinel

1、Sentinel 是什么?核心作用

是阿里开源的流量控制、熔断降级、系统防护组件,解决微服务架构中的流量峰值、依赖不稳定、 服务雪崩问题。 核心:流量控制、熔断降级、系统自适应保护、热点参数限流。

2、Sentinel 和 Hystrix 的区别

设计目标:Sentinel 侧重流量控制 + 熔断降级 + 系统防护,更全面;Hystrix 侧重熔断降级 + 线程 隔离。 隔离策略:Sentinel 用信号量隔离(轻量,无额外线程开销);Hystrix 支持线程池 / 信号量隔 离,线程池隔离会增加上下文切换开销。 熔断策略:Sentinel 支持慢调用比例、异常比例、异常数;Hystrix 仅异常比例 / 异常数。 生态:Sentinel 适配 Dubbo、Spring Cloud、网关等;Hystrix 停止维护,Spring Cloud 推荐替换 为 Sentinel。

3、Sentinel 的核心工作流程

资源定义→规则加载(流控 / 熔断 / 系统)→调用时拦截统计(QPS、响应时间、异常)→触发 规则→执行限流 / 降级 / 熔断逻辑→返回兜底结果。

4、Sentinel 流控的核心指标

  1. 三大核心监控指标
  2. QPS:每秒请求数(最常用)
  3. 线程数:调用该接口的并发线程数
  4. 响应时间 RT:平均响应时间,用来做慢调用降级
  5. 流控规则依据的指标 阈值:QPS 或线程数的上限 阈值类型: QPS 线程数 流控模式: 直接:当前接口达到阈值直接限流 关联:关联接口达到阈值,限流当前接口 链路:只针对某一条调用链路限流 流控效果: 快速失败 WarmUp(预热,防止冷启动) 排队等待(匀速排队)
  6. 降级规则依据的指标 RT:平均响应时间 异常比例:异常占比 异常数:单位时间异常次数

5、流控的三种策略

直接:对当前资源直接限流(如接口 QPS≤1000)。 关联:两个资源关联,A 触发限流则 B 也限流(如订单支付和订单查询,支付高峰时限制查询)。 链路:只限制从指定入口进入的流量(如从首页进入的商品详情请求限流)。

6、流控的三种效果

快速失败:超过阈值直接拒绝,抛FlowException (默认)。 预热(Warm Up):冷启动时逐步放开流量,避免系统瞬间被打满(适合启动后流量突增场 景)。 排队等待:请求排队,超过超时时间拒绝(适合流量均匀、允许短暂等待的场景)。

7、Sentinel 熔断的三种触发策略

慢调用比例:请求响应时间 > 阈值的比例超过设定值(如 > 500ms 的请求占比≥30%)。 异常比例:异常请求占总请求的比例超过设定值(如异常率≥20%)。 异常数:单位时间内异常请求数超过阈值(如 1 分钟内异常≥50 次)。

8、熔断的状态转换

关闭(Closed):正常调用,统计请求数据。 打开(Open):触发熔断规则,直接拒绝所有请求,进入降级。 半开(Half-Open):熔断超时后,放行少量请求测试;若正常则恢复关闭,否则重新打开。

9、降级的兜底逻辑

BlockHandler:处理限流 / 熔断触发的阻塞(如FlowException 、DegradeException )。 Fallback:处理业务异常(如空指针、数据库报错),也可兼容阻塞异常。 区别:BlockHandler 专注流量规则触发,Fallback 专注业务异常。

10、热点参数限流是什么?应用场景

热点参数限流:针对接口里某个高频访问的参数值单独限流,而不是整个接口一起限流。 比如:根据用户 ID、商品 ID、IP限流,只限制 “热门参数”,不影响其他人。

典型应用场景

  1. 商品详情页防刷 某个爆款商品 ID 被大量请求 只对这个商品 ID限流,不影响其他商品
  2. 用户防刷 / 防爬虫 某个用户 / IP 疯狂请求 只限制这个用户 ID/IP,不影响接口
  3. 热点搜索词限流 某个关键词被大量搜索 只限制该关键词
  4. 防恶意攻击 针对某个参数值的恶意请求 精准限流,不影响正常用户

11、系统自适应保护规则

系统自适应保护(System Protection)是 Sentinel 从应用整体维度监控系统负载,当 Load/CPU/RT/ 线程数 / 入口 QPS任一指标触达阈值时,自动对入口流量限流,防止系统过载崩溃 Sentinel。

核心特点:

全局维度:作用于整个应用,而非单个接口 / 资源Sentinel 入口流量:仅对EntryType.IN (Web/Dubbo 服务端)生效Sentinel 自适应:基于系统实时负载动态控流,而非固定阈值 兜底保护:最后一道防线,防止雪崩

指标

触发条件

适用场景

参考值

系统 Load

(Load1)

Load1 > 阈值且并发线程数 > 系统容量 Linux/Unix 服务 器 CPU 核心数 × 2.5Sentinel

CPU 使用率

CPU 使用率 > 阈值 (0.0~1.0) 全平台,1.5.0+ 支持 0.8(80%)

平均 RT

入口流量平均响应时间 > 阈值 (ms) 慢调用导致系统 阻塞 500~1000 ms

并发线程数

入口总并发线程数 > 阈值 线程池耗尽、请 求堆积 CPU 核心数 × 2~4

入口 QPS

入口总 QPS > 阈值 流量洪峰、防刷 按单机容量评估

  1. 代码配置
  2. Dashboard配置
  3. 进入 Sentinel 控制台→系统规则
  4. 点击新增系统规则
  5. 填写 5 大指标阈值,保存即可
importcom.alibaba.csp.sentinel.slots.system.SystemRule;
importcom.alibaba.csp.sentinel.slots.system.SystemRuleManager;
importjava.util.ArrayList;
importjava.util.List;
publicclassSystemRuleConfig {
publicstaticvoidinitSystemRules() {
List<SystemRule>rules=newArrayList<>();
SystemRulerule=newSystemRule();
// 1. Load 阈值(Linux 有效)
rule.setHighestSystemLoad(8.0); // 8核CPU参考:8×2.5=20
// 2. CPU 使用率阈值(0.8=80%)
rule.setHighestCpuUsage(0.8);
// 3. 平均 RT 阈值(ms)
rule.setAvgRt(800);
// 4. 并发线程数阈值
rule.setMaxThread(200);
// 5. 入口 QPS 阈值
rule.setQps(1000);
rules.add(rule);
SystemRuleManager.loadRules(rules);
}
}

方案

依赖模块

优点

缺点

适用场景

Nacos

sentinel- datasource- nacos 动态推送、易集 成、生态成熟 需部署 Nacos 生产环境、 Spring Cloud 生 态

Apollo

sentinel- datasource- apollo 多环境隔离、权限 管控、版本回溯 配置较重 企业级多环境、 强管控

ZooKeeper

sentinel- datasource- zookeeper 强一致性、监听机 制成熟 部署维护成本 高 已有 ZK 生态、 强一致需求

Redis

sentinel- datasource- Redis 高性能、读写快 无原生监听、 需自行实现 实时性要求高、 轻量部署

本地文件

内置 零依赖、配置简单 不支持集群、 不可动态推送 开发 / 测试环境

12、Sentinel 的持久化规则

默认规则存内存,重启丢失;生产需持久化到Nacos、ZooKeeper、Apollo等配置中心。

实现:通过ReadableDataSource / WritableDataSource 对接配置中心,实现规则同步。

13、Sentinel 如何集成到 Spring Cloud/Dubbo

Spring Cloud:引入spring-cloud-starter-alibaba-sentinel ,配置
spring.cloud.sentinel.enabled=true ,接口加@SentinelResource 。

Dubbo:引入sentinel-dubbo-adapter ,自动拦截 Dubbo 服务,配置流控 / 熔断规则。

14、Sentinel 的底层统计原理

Sentinel 底层用 LeapArray 实现滑动时间窗口,把 1s 切成多个小窗口,用无锁原子计数做高性能统 计,支持秒级精准 QPS / 线程 / RT / 异常监控。

  1. 核心数据结构

LeapArray(跳跃数组)

把1 秒切成多个小时间窗口(默认:1s 分 2 个窗口,每个 500ms) 每个窗口存: pass /block/total 请求数 RT、异常数、成功数等 结构:数组 + 原子计数器→无锁、高性能 2. 滑动窗口工作流程

  1. 当前时间→计算落在哪个小窗口
  2. 如果是旧窗口:复用 / 重置
  3. 如果是新窗口:创建新窗口
  4. 统计时:把所有有效窗口的值相加→得到 1s 总 QPS 这就是为什么 Sentinel 能秒级限流、不丢统计、性能极高。

15、生产中 Sentinel 如何配置?核心注意点

生产 Sentinel 核心:规则持久化、网关限流、系统自适应保护、热点参数限流、兜底轻量化、监控可观 测、阈值基于压测,保证高可用不误杀。 一、生产核心配置(必配)

  1. 规则必须持久化 不用内存规则,必须用Nacos / Apollo 流控、降级、热点、系统、授权规则全部持久化 控制台修改→推送到配置中心→所有实例同步
  2. 接入限流埋点 Web 接口、Feign、Gateway、Dubbo 都要适配
自定义核心接口用@SentinelResource
  1. 限流降级兜底返回
blockHandler :处理限流 / 熔断
fallback :处理业务异常

不能吞异常,必须返回友好提示

  1. 网关流控(必开) Spring Cloud Gateway 全局限流 路由维度 + 自定义参数维度 防止流量直接打崩下游微服务
  2. 系统自适应保护(必开) CPU 使用率 ≤ 80% Load 阈值 ≤ CPU 核心数 × 2.5 最大并发线程数 平均 RT 阈值
  3. 热点参数限流(高频接口必配) 商品 ID、用户 ID、IP、搜索词 防止热点参数打崩接口
  4. 日志与监控
开启sentinel.log日志

对接 Prometheus + Grafana 限流、降级、熔断、异常全部可观测

@SentinelResource(
fallback="fallbackMethod",
blockHandler="blockHandler"
)

二、生产核心注意点(重中之重,面试必问)

  1. 规则不能乱设,必须先压测 QPS、线程数、RT 必须基于压测结果 不能凭感觉设阈值,否则会误杀正常流量
  2. 重试接口一定要注意幂等 Sentinel 限流 / 降级会触发重试 非幂等接口(新增 / 支付)不能随便重试
  3. 兜底逻辑必须轻量化 不能写复杂逻辑、不能查 DB、不能调远程服务 否则兜底本身也会崩
  4. 集群限流要单独配置 单机限流 ≠ 集群限流 大促、秒杀必须开集群限流
  5. 网关限流 > 服务限流 流量尽量在网关层拦住 不要让无效流量打到后端服务
  6. 系统规则是全局保护,优先级最高 只要 Load/CPU 超了,直接拦入口流量 保护系统不被打满卡死
  7. 规则变更必须灰度 不能一次性全量推 先单实例→再集群
  8. Sentinel 控制台不能直接生产裸奔 必须登录权限 规则修改要留日志 重要规则需审核
  9. 避免循环依赖、异步上下文丢失 异步代码需要手动传递上下文 否则限流不生效
  10. 生产环境关闭不必要的心跳日志 避免日志疯狂打盘 IO

7、Gateway

1、什么是 Spring Cloud Gateway

Spring Cloud Gateway(SCG)是 Spring 官方出品的、基于Spring WebFlux(Netty 异步非阻塞) 实现的微服务 API 网关。

核心作用:

  1. 请求路由:统一入口,转发到对应微服务
  2. 负载均衡:结合注册中心(Nacos/Eureka)自动负载均衡
  3. 统一鉴权:登录、权限判断在网关统一处理
  4. 限流熔断:结合 Sentinel 或内置过滤器做限流、降级
  5. 日志、跨域、请求响应修改:统一处理请求头、响应头、日志等

三大核心概念:

  1. Route(路由) 一组规则:id + uri + predicates + filters
  2. Predicate(断言) 判断请求是否匹配,匹配才转发
  3. Filter(过滤器) 对请求 / 响应做修改、增强

优点:

异步非阻塞,性能高 与 Spring Cloud 生态无缝整合 配置简单,支持动态路由 内置丰富断言、过滤器

2、Gateway 和 Zuul 的区别

Zuul1 基于 Servlet 同步阻塞,性能较低; Spring Cloud Gateway 基于 WebFlux 异步非阻塞,性能更高、功能更强,是 Spring Cloud 官方推荐的 网关实现。

3、Gateway 三大核心概念

Route(路由)

网关的基本转发单元 组成:id + 目标 uri + 断言 + 过滤器 作用:根据规则把请求转发到对应微服务

Predicate(断言)

匹配条件

判断请求是否符合路由规则 比如:Path、Method、Header、Query、Host 等 只有断言为 true,路由才会生效

Filter(过滤器)

请求 / 响应的增强与修改 分为:GlobalFilter、GatewayFilter 作用:鉴权、限流、加请求头、修改响应、日志等

4、Gateway 执行流程

  1. 客户端请求→到达 Gateway
  2. HandlerMapping 匹配路由 根据Predicate(断言)找到对应的 Route
  3. 找到对应的 WebHandler 交给FilteringWebHandler处理
  4. 组装过滤器链 把GlobalFilter + GatewayFilter按顺序合并成一条链 5.执行前置过滤器(Pre 过滤器) 鉴权、限流、请求头修改、路由跳转等 6.转发请求到微服务 通过 Netty 异步调用后端服务 7.执行后置过滤器(Post 过滤器) 修改响应、日志、跨域、加密等 8.返回结果给客户端

5、Gateway 过滤器生命周期

Gateway 过滤器生命周期分为 pre(转发前)和 post(响应后),pre 正序执行,post 倒序执行。 所有 pre 按 order 从小到大执行

所有 post 按 order 从大到小执行

  1. pre(前置) 请求转发到微服务之前执行 做:鉴权、限流、请求头修改、日志、路由处理
  2. post(后置) 微服务返回响应之后执行 做:修改响应头、加密、日志、跨域处理

6、过滤器分类

局部过滤器:对单个路由生效 全局过滤器:对所有路由生效(例:全局鉴权、全局跨域、全局日志)

7、常用 Predicate 有哪些

Gateway 常用 Predicate 核心五大类:路径 (Path)、方法 (Method)、头信息 (Header)、参数

(Query)、主机 (Host),辅以地址 (RemoteAddr)、时间 (Between) 等高级条件。

Path:按路径匹配

规则:/user/** 、/order/{id}**

Method:按请求方法匹配

规则:GET 、POST 、PUT

Header:按请求头匹配

规则:X-Request-Id=\d+

Query:按请求参数匹配

规则:name=\w+ 、age=18

RemoteAddr:按 IP 匹配

规则:192.168.1.0/24

Host:基于域名匹配,支持通配符

规则:*.test.com 、www.example.com

After/Before/Between:按时间匹配

规则:Between 2024-01-01T00:00:00+08:00  , 2024-12-31T23:59:59+08:00

Cookie:基于 Cookie 匹配,较少用

规则:token=abc\d+

8、常用内置 Filter

头增删改、路径改、参数加、限流熔断、重定向。

1、请求头类 AddRequestHeader:添加请求头 RemoveRequestHeader:删除请求头 SetRequestHeader:覆盖请求头 2、响应头类 AddResponseHeader:添加响应头 RemoveResponseHeader:删除响应头 SetResponseHeader:覆盖响应头 3、路径处理 PrefixPath:添加前缀路径 StripPrefix:去掉前缀(最常用) RewritePath:重写路径(正则) 4、参数处理 AddRequestParameter:添加请求参数 5、限流、熔断 RequestRateLimiter:网关限流 CircuitBreaker:熔断 6、其他常用 RedirectTo:重定向 SetStatus:设置响应状态码

9、Gateway 如何实现限流

Gateway 限流常用两种:内置 RequestRateLimiter(Redis 令牌桶),和整合 Sentinel(功能更

强,支持降级热点),生产一般用 Sentinel。

Gateway 限流主要有两种方案:

  1. 内置 RequestRateLimiter + Redis + Lua(生产最常用)
  2. 整合 Sentinel 网关限流(更强大、支持热点、降级、系统保护) 内置限流(Redis + Token Bucket)
  3. 原理 基于令牌桶算法 使用Redis + Lua 脚本保证原子性 按IP / 用户 / 接口维度限流
  4. 三大关键组件 KeyResolver:限流维度(取谁来限流) 按 IP 按用户 ID 按接口路径 RateLimiter:令牌桶实现 Redis:存储桶状态
  5. 实现步骤
  6. 引入依赖
  7. 写 KeyResolver(以 IP 限流为例)
  8. yml 配置
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-Redis-reactive</artifactId>
</dependency>
@Bean
publicKeyResolveripKeyResolver() {
returnexchange->Mono.just(
Objects.requireNonNull(exchange.getRequest().getRemoteAddress()).getAddress
().getHostAddress()
);
}
spring:
cloud:
gateway:

二、Sentinel 网关限流(推荐生产)

  1. 优势 支持QPS、线程数、热点参数、系统自适应 支持降级、熔断、授权、黑白名单 规则可持久化、支持控制台
  2. 实现
  3. 引入依赖
  4. 配置 Sentinel 地址
  5. 直接在Sentinel 控制台配置网关流控规则即可。

10、限流维度

Gateway 限流维度:IP、用户 ID、接口路径、全局限流,生产最常用 IP + 接口路径。

  1. 按 IP 限流(最常用) 同一个 IP 每秒最多访问 N 次 防爬虫、防刷接口
  2. 按用户 ID / Token 限流 针对登录用户限流 按用户粒度控制频率
  3. 按接口路径 / 路由限流 某个接口单独限流 比如:/order/** 限流更严格
  4. 按全局 / 全网关限流
routes:
- id: user-service
uri: lb://user-service
predicates:
- Path=/user/**
filters:
- name: RequestRateLimiter
args:
key-resolver: "#{@ipKeyResolver}"
Redis-rate-limiter.replenishRate: 10   # 令牌生成速度
Redis-rate-limiter.burstCapacity: 20   # 桶容量
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel-
gateway</artifactId>
</dependency>
spring.cloud.sentinel.transport.dashboard=ip:8080

整个网关总 QPS 限制 保护网关自身不被打崩

11、Gateway 跨域怎么做

Gateway 解决跨域首选代码配置CorsWebFilter ,设置允许的来源、请求头、方法,并开启凭证,同

时前端必须配合开启withCredentials 。

方案一:配置类(推荐,最灵活)

直接编写一个CorsConfigurationSource的 Bean,优先级最高,覆盖默认配置。

代码实现

importorg.springframework.context.annotation.Bean;
importorg.springframework.context.annotation.Configuration;
importorg.springframework.web.cors.CorsConfiguration;
importorg.springframework.web.cors.reactive.CorsWebFilter;
importorg.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
importorg.springframework.web.util.pattern.PathPatternParser;
importjava.util.Collections;
@Configuration
publicclassCorsConfig {
@Bean
publicCorsWebFiltercorsFilter() {
CorsConfigurationconfig=newCorsConfiguration();

// 1. 允许的来源(生产环境不要用 *,指定具体域名)

config.addAllowedOrigin("http://localhost:8080");
config.addAllowedOrigin("https://your-frontend.com");
// 2. 允许携带 Cookie(必须配合前端 withCredentials = true)
config.setAllowCredentials(true);
// 3. 允许的请求头(* 代表所有,也可指定如 Authorization)
config.addAllowedHeader("*");
// 4. 允许的请求方法(GET, POST, PUT, DELETE 等)
config.addAllowedMethod("*");
// 5. 预检请求(OPTIONS)的有效期,单位秒
config.setMaxAge(3600L);
// 6. 应用规则到所有路径
UrlBasedCorsConfigurationSourcesource=new
UrlBasedCorsConfigurationSource(newPathPatternParser());
source.registerCorsConfiguration("/**", config);
returnnewCorsWebFilter(source);
}
}

方案二:Yml 配置(简单快捷)

如果配置较简单,可直接在application.yml中配置。 三、前端配合关键点

后端配置好了,前端必须这样写,否则跨域依然失败!

  1. AJAX 请求
  2. Fetch 请求 四、面试 / 生产避坑点
1. allowedOrigins不能用*
如果设置了allowCredentials: true ,allowedOrigins必须是具体的域名,不能是
  • ,否则浏览器会拦截。
  1. 预检请求(OPTIONS) 复杂请求(如自定义 Header、Put/Delete)会先发送一个 OPTIONS 询问,Gateway 必须自 动处理该请求并返回 200。

Gateway 内置处理器会自动处理 OPTIONS,无需手动写接口。

  1. 顺序问题 自定义CorsWebFilter的优先级高于 Yml 配置,使用时注意冲突。

12、网关超时配置

  1. 全局超时配置
对所有路由生效,推荐在spring.cloud.gateway.httpclient下配置:
spring:
cloud:
gateway:
globalcors:
cors-configurations:
'[/**]': # 匹配所有请求
allowedOrigins: "http://localhost:8080"# 允许的域名
allowedHeaders: "*"# 允许所有头
allowedMethods: "*"# 允许所有方法
allowCredentials: true # 允许携带Cookie
maxAge: 3600 # 预检缓存时间
axios.get('http://api.your-domain.com/user', {
withCredentials: true  // <-- 必须加这行,允许携带 Cookie
})
fetch('http://api.your-domain.com/user', {
credentials: 'include'  // <-- 必须加这行
})
  1. 路由级超时配置 在路由metadata中单独配置,覆盖全局,单位均为毫秒:
  2. Java 代码配置(DSL 方式) 通过 RouteLocator 硬编码配置,适合动态路由场景:
spring:
cloud:
gateway:
httpclient:

连接超时:5秒(单位:毫秒)

connect-timeout: 5000
# 响应超时:10秒(Duration格式,推荐)
response-timeout: 10s

连接池配置(可选,优化长连接)

pool:
max-idle-time: 60000    # 连接最大空闲时间(毫秒)
max-life-time: 120000   # 连接最大存活时间(毫秒)
spring:
cloud:
gateway:
routes:
- id: order-service
uri: lb://order-service
predicates:
- Path=/order/**
metadata:
# 该路由连接超时:3秒(毫秒)
connect-timeout: 3000
# 该路由响应超时:8秒(毫秒)
response-timeout: 8000
importorg.springframework.cloud.gateway.route.RouteLocator;
importorg.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
importorg.springframework.context.annotation.Bean;
importorg.springframework.context.annotation.Configuration;
importstatic
org.springframework.cloud.gateway.support.RouteMetadataUtils.CONNECT_TIMEOUT
_ATTR;
importstatic
org.springframework.cloud.gateway.support.RouteMetadataUtils.RESPONSE_TIMEOU
T_ATTR;
@Configuration
publicclassGatewayConfig {
@Bean
publicRouteLocatorcustomRouteLocator(RouteLocatorBuilderbuilder) {
returnbuilder.routes()
.route("order-route", r->r
.path("/order/**")
.uri("lb://order-service")
// 路由级超时(毫秒)
  1. WebFlux 全局超时(兜底) 网关基于 WebFlux,需确保response-timeout小于 WebFlux 全局超时,否则网关超时不生效:

13、网关统一异常处理

Gateway 统一异常处理通过继承 DefaultErrorWebExceptionHandler 实现,捕获全局异常并统一返回 JSON 格式,保证前端格式一致。

  1. 实现方式(生产标准方案) 继承DefaultErrorWebExceptionHandler重写,覆盖默认异常处理。
.metadata(CONNECT_TIMEOUT_ATTR, 3000)
.metadata(RESPONSE_TIMEOUT_ATTR, 8000)
)
.build();
}
}
spring:
webflux:
# WebFlux全局响应超时:15秒(必须 > Gateway的response-timeout)
response-timeout: 15s
importorg.springframework.boot.autoconfigure.web.WebProperties;
import
org.springframework.boot.autoconfigure.web.reactive.error.DefaultErrorWebExc
eptionHandler;
importorg.springframework.boot.web.reactive.error.ErrorAttributes;
importorg.springframework.context.ApplicationContext;
importorg.springframework.http.HttpStatus;
importorg.springframework.http.MediaType;
importorg.springframework.web.reactive.function.BodyInserters;
importorg.springframework.web.reactive.function.server.*;
importjava.util.HashMap;
importjava.util.Map;
publicclassGatewayExceptionHandlerextendsDefaultErrorWebExceptionHandler
{
publicGatewayExceptionHandler(ErrorAttributeserrorAttributes,
WebPropertieswebProperties,
ApplicationContextapplicationContext) {
super(errorAttributes, webProperties.getResources(),
applicationContext);
}
@Override
protectedRouterFunction<ServerResponse>
getRoutingFunction(ErrorAttributeserrorAttributes) {
returnRouterFunctions.route(RequestPredicates.all(),
this::renderErrorResponse);
}
  1. 注入容器
  2. 能捕获哪些异常?
  3. 网关504 超时
  4. 限流 / 熔断(Sentinel 降级)
  5. 服务未找到 404
  6. 路由配置错误
  7. 过滤器内抛出的异常
  8. 网络异常、连接拒绝
// 统一返回 JSON
privateServerResponserenderErrorResponse(ServerRequestrequest) {
Map<String, Object>error=getErrorAttributes(request,
getErrorAttributeOptions(request, MediaType.ALL));
intstatus=getHttpStatus(error);
Stringmsg= (String) error.get("message");
Map<String, Object>result=newHashMap<>();
result.put("code", status);
result.put("msg", msg);
result.put("data", null);
returnServerResponse
.status(HttpStatus.OK)
.contentType(MediaType.APPLICATION_JSON)
.body(BodyInserters.fromValue(result));
}
}
import
org.springframework.boot.context.properties.EnableConfigurationProperties;
importorg.springframework.boot.autoconfigure.web.WebProperties;
importorg.springframework.context.annotation.Bean;
importorg.springframework.context.annotation.Configuration;
importorg.springframework.boot.web.reactive.error.ErrorAttributes;
importorg.springframework.boot.web.reactive.error.ErrorWebExceptionHandler;
@Configuration
@EnableConfigurationProperties(WebProperties.class)
publicclassGatewayExceptionConfig {
@Bean
publicErrorWebExceptionHandlererrorWebExceptionHandler(ErrorAttributes
errorAttributes,WebPropertieswebProperties,ApplicationContextcontext) {
returnnewGatewayExceptionHandler(errorAttributes, webProperties,
context);
}
}

14、Gateway 支持负载均衡吗

Spring Cloud Gateway 集成了 Ribbon,使用 lb:// 服务名即可自动基于注册中心 + Spring Cloud LoadBalancer 实现负载均衡,默认轮询策略。 底层原理:

  1. Gateway 收到请求
  2. 根据lb://服务名去注册中心获取实例列表
  3. 通过LoadBalancer选择一个实例(轮询 / 随机等)
  4. 转发请求 负载均衡策略: 默认:轮询 支持:随机、权重、同集群优先等

15、Gateway 为什么性能高

Spring Cloud Gateway 基于 WebFlux 和 Netty 异步非阻塞模型,采用响应式编程,不需要为每个请

求创建独立线程,资源占用少、吞吐量高,所以性能远超 Zuul1。

  1. 底层模型(最关键) 基于 Spring WebFlux,不是传统 Servlet

基于 Netty 异步非阻塞

少量线程就能支撑高并发,不会因为请求多就创建大量线程 2. 非阻塞 I/O 连接、读写、响应全程非阻塞 一个线程可以同时处理成千上万请求 不会阻塞等待下游服务返回 3. 响应式编程 全链路响应式(Mono / Flux) 资源占用极低、吞吐量极高 4. 对比 Zuul1(秒懂) Zuul1:Servlet 同步阻塞,来一个请求开一个线程,高并发容易卡死 Gateway:异步非阻塞 + 事件驱动,少量线程扛高 QPS

spring:
cloud:
gateway:
routes:
- id: user-service
uri: lb://user-service  # 这里开启负载均衡
predicates:
- Path=/user/**

16、Gateway 核心作用

  1. 统一入口:所有微服务只暴露网关一个出口
  2. 路由转发:根据路径 / 域名 / 参数转发到对应服务
  3. 负载均衡:自动实现服务调用负载均衡
  4. 统一鉴权:登录、权限、黑白名单在网关统一处理
  5. 限流熔断:流量控制、降级保护后端服务
  6. 日志监控:请求日志、接口监控、调用链统一收集
  7. 跨域 / 协议转换:统一处理跨域、HTTPS 等

八、网络

1、Netty

1、为什么Netty适合做网络编程?

Netty是一个基于Java的高性能网络编程框架,它被广泛用于构建可扩展的、高性能的网络应用程序。以 下是一些原因说明为什么Netty适合做网络编程:

  1. 强大的抽象和组件:Netty提供了一组强大的抽象和可重用的组件,如事件模型、处理器链、编解 码器等。这些组件使得网络编程变得更加简单和灵活,开发者可以根据需求自由组合和定制。
  2. 高性能:Netty采用了基于事件驱动的异步非阻塞IO模型,利用了Java NIO(New IO)的特性,能 够处理大量的并发连接。它的线程模型和内存管理机制也经过优化,能够最大限度地提高网络应用 程序的性能和吞吐量。
  3. 完善的协议支持:Netty提供了丰富的协议支持,包括TCP、UDP、HTTP、WebSocket等。它内置 了许多常用的协议编解码器,可以轻松地进行协议的解析和编码,简化了网络应用程序的开发过 程。
  4. 可扩展性:Netty的设计非常灵活,支持自定义的协议、编解码器和处理器。它提供了丰富的扩展 点和钩子函数,可以方便地进行功能扩展和定制,满足各种复杂的业务需求。
  5. 成熟稳定:Netty是一个成熟稳定的开源项目,经过了广泛的实际应用和验证。它拥有活跃的社区 和强大的生态系统,提供了大量的文档、示例和工具,使得开发者能够快速上手并解决问题。 综上所述,Netty具有强大的抽象和组件、高性能、完善的协议支持、可扩展性和成熟稳定等特点,使其 成为一种优秀的选择用于网络编程。

2、Netty性能好的原因是什么?

Netty 是一个高性能的网络应用框架,其性能好的原因主要有以下几点:

  1. 异步非阻塞:Netty 使用异步非阻塞的 I/O 模型,可以处理大量的并发连接而不需要为每个连接分 配一个线程。这种模型减少了线程切换的开销,提高了系统的吞吐量和响应速度。
  2. 高度可定制化:Netty 提供了丰富的可定制化选项,可以根据应用的需求进行灵活的配置。开发者 可以自定义编解码器、处理器链、线程模型等,以适应不同的网络应用场景。
  3. 零拷贝:Netty 支持零拷贝技术,可以避免数据在内存之间的多次拷贝,减少了内存的使用和数据 传输的开销,提高了性能。
  4. 内存管理优化:Netty 使用了内存池技术,可以重用内存,减少了频繁的内存分配和回收操作,提 高了内存的利用率和性能。
  5. 模块化设计:Netty 的设计模块化,各个功能模块之间解耦,可以根据需要选择性地使用特定的模 块,避免了不必要的性能损耗。 总的来说,Netty 的高性能得益于其异步非阻塞的 I/O 模型、可定制化的设计、零拷贝技术、内存管理 优化和模块化架构等多个方面的优势。这些特性使得 Netty 成为开发高性能网络应用的理想选择。

3、Netty的零拷贝是怎么实现的?

Netty 实现零拷贝主要依赖于以下两个技术:

  1. 零拷贝文件传输:Netty 使用了操作系统提供的零拷贝机制,例如 Linux 下的 sendfile 和 splice 系 统调用。这些系统调用可以直接将文件数据从磁盘读取到网络套接字,或者从一个套接字传输到另 一个套接字,而无需经过用户空间和内核空间之间的数据拷贝。
  2. 零拷贝内存传输:Netty 使用了 Direct Memory Buffer(直接内存缓冲区)来实现零拷贝内存传 输。直接内存缓冲区是一种直接分配在堆外内存的缓冲区,可以通过操作系统的文件描述符直接读 写数据,避免了数据在用户空间和内核空间之间的拷贝。 通过使用这些零拷贝技术,Netty 在进行数据传输时可以避免将数据从一个缓冲区拷贝到另一个缓冲 区,从而减少了数据拷贝的次数和数据在内存之间的传输开销。这样可以提高数据传输的效率和性能。 需要注意的是,零拷贝并不是在所有情况下都能完全避免数据拷贝。在某些情况下,仍然需要进行少量 的数据拷贝操作,例如数据的解码和编码过程。但是相比传统的拷贝方式,Netty 的零拷贝机制可以最 大程度地减少数据拷贝的次数,提高了性能。

4、能不能说一说Netty的无锁化设计?

Netty 的无锁化设计是指在多线程环境下,尽量减少对共享数据的锁使用,以避免锁竞争和线程阻塞, 从而提高系统的并发性能。Netty 在实现无锁化设计时主要采用了以下几种技术:

  1. 并发容器:Netty 使用了并发容器来替代传统的线程安全集合类。例如,使用 ConcurrentMap 替 代 HashMap,使用 ConcurrentLinkedQueue 替代 LinkedList,这些并发容器底层使用了 CAS (Compare and Swap)等无锁算法来实现线程安全。
  2. 原子操作:Netty 使用了原子操作来实现对共享数据的无锁访问。原子操作是一种不可中断的操 作,可以保证在多线程环境下对共享数据的操作是原子性的。Netty 使用了 Java 提供的原子类,如 AtomicBoolean、AtomicInteger 等,来实现无锁访问。
  3. 事件驱动模型:Netty 的核心思想是基于事件驱动的模型,通过事件的发布和订阅来实现线程间的 解耦和通信。这种模型避免了线程间的锁竞争,每个线程只需要处理自己感兴趣的事件,大大提高 了系统的并发性能。
  4. 非阻塞 I/O:Netty 使用了非阻塞的 I/O 模型,通过异步的方式处理网络 I/O 操作,避免了线程在 等待 I/O 完成时的阻塞,提高了系统的并发性能。 通过这些无锁化设计的技术手段,Netty 在多线程环境下能够更好地利用计算资源,提高系统的并发性 能和可伸缩性。同时,无锁化设计也减少了线程间的竞争和线程阻塞,避免了潜在的死锁和性能瓶颈问 题。

5、Netty的线程模型是怎么样的?

Netty的线程模型是基于事件驱动的,它采用了多线程池的架构来处理网络请求和事件。以下是Netty的 线程模型的主要特点:

  1. Boss线程池(Acceptors):这个线程池用于处理新的连接请求,通常会绑定到一个端口,并且负 责接受客户端的连接。每个Boss线程都会监听一个独立的套接字,用于接受客户端的连接请求。
  2. Worker线程池(EventLoopGroup):一旦连接建立,客户端的请求会被传递给Worker线程池 中的一个EventLoop进行处理。Worker线程池负责处理I/O事件,如读取和写入数据,以及执行用 户定义的业务逻辑。Netty通常会有多个Worker线程,每个线程都会处理多个连接,通过事件循环 (EventLoop)来处理这些连接上的事件。
  3. EventLoop(事件循环):每个Worker线程都包含一个EventLoop,它负责处理一个或多个连接 上的事件。EventLoop会持续地从事件队列中获取事件,然后执行相应的操作,比如读取数据、处 理请求、写入数据等。每个连接都会被分配到一个特定的EventLoop,确保了事件的顺序性和线程 的安全性。
  4. 任务队列:Netty使用任务队列来存储需要处理的事件,这些事件可以是读写操作、用户自定义的 任务或其他事件。这些事件会被EventLoop从队列中取出并执行。 总体来说,Netty的线程模型允许多个连接共享同一个线程,避免了线程创建和销毁的开销,提高了系统 的性能和效率。通过事件驱动的方式,Netty能够高效地处理大量的并发连接,适用于构建高性能、可扩 展的网络应用程序。需要注意的是,具体的线程数目和配置可以根据应用程序的需求进行调整。

6、Netty如何解决TCP粘包、拆包的问题的?

Netty提供了多种解决TCP粘包和拆包问题的机制,帮助开发者处理在网络传输过程中可能出现的数据分 片问题。这些机制可以确保数据在发送和接收时能够正确地分割和组装,从而避免粘包和拆包的困扰。 以下是Netty解决TCP粘包和拆包问题的一些常见方法:

  1. 固定长度解码器(FixedLengthFrameDecoder):这个解码器会根据指定的固定长度对接收到 的数据进行切割。无论数据内容如何,都会按照固定长度进行拆分,从而确保每个数据包的长度是 一致的。
  2. 行尾分隔符解码器(LineBasedFrameDecoder):适用于基于文本协议的场景,该解码器会根 据行尾分隔符(如换行符)将数据切分为不同的数据包。这样,每个数据包都会包含一行完整的文 本。
  3. 分隔符解码器(DelimiterBasedFrameDecoder):类似于行尾分隔符解码器,但可以自定义分 隔符。开发者可以指定特定的字节序列作为分隔符,用于切分数据。
  4. 自定义解码器:Netty还允许开发者根据具体的协议和业务需求创建自定义的解码器。这样可以更 灵活地处理数据的分割和组装。 这些解码器通常作为ChannelPipeline中的一部分,用于解决数据在网络传输过程中可能引发的粘包和拆 包问题。开发者可以根据自己的需求选择合适的解码器,或者结合多种解码器来处理不同类型的数据。 需要注意的是,虽然这些解码器可以很好地处理大部分粘包和拆包问题,但在一些复杂的情况下可能仍 需要开发者进行额外的处理和调优。

7、Netty的Buffer为什么好用

Netty的Buffer在网络编程中被认为非常好用,有以下几个方面的优势:

  1. 内存管理优化:Netty的Buffer实现了内存池技术,能够有效地管理内存的分配和释放。这可以减 少频繁的内存分配和垃圾回收,从而提高性能和减少延迟。
  2. 零拷贝技术:Netty的Buffer支持零拷贝(Zero-Copy)技术,这意味着在数据传输过程中可以避免 不必要的数据拷贝,减少了CPU和内存的负担,提高了数据传输的效率。
  3. 支持多种数据类型:Netty的Buffer提供了多种类型的Buffer,如堆内存缓冲区(Heap Buffer)和 直接内存缓冲区(Direct Buffer),可以根据需要选择合适的Buffer类型来优化性能。
  4. 灵活的API:Netty的Buffer提供了丰富的操作方法,可以轻松地进行数据读写、切片、复制等操 作,使得处理数据变得更加方便和灵活。
  5. 与ChannelPipeline集成:Netty的Buffer与ChannelPipeline紧密集成,可以方便地在不同的处理 器(如编码器、解码器、处理器等)之间传递数据,简化了数据处理的流程。
  6. 可扩展性和定制性:Netty允许开发者基于自己的需求扩展和定制Buffer的行为,从而实现更高级 别的功能和优化。 综上所述,Netty的Buffer在性能、内存管理、数据传输效率以及灵活性方面的优势,使得它成为了网络 编程中一个非常实用和强大的工具。无论是处理小规模数据还是大规模数据,Netty的Buffer都能够有效 地提升网络应用的性能和可靠性。

8、说说 Netty 的对象池技术?

Netty的对象池技术是一种用于管理和重用对象的机制,旨在提高内存使用效率和性能。在网络编程中, 频繁地创建和销毁对象可能会导致内存碎片化和额外的垃圾回收开销,从而影响应用程序的性能。Netty 引入了对象池技术来缓解这些问题。 在Netty中,对象池主要用于管理两种类型的对象:ByteBuf(字节缓冲区)和ChannelHandlerContext (通道处理上下文)。以下是关于Netty对象池技术的一些关键点:

  1. ByteBuf对象池:Netty的ByteBuf是用于处理网络数据的字节缓冲区。通过使用ByteBuf对象池, Netty可以重用已经分配的字节缓冲区,避免频繁地创建和销毁这些对象。这有助于减少内存分配 和垃圾回收的开销,提高数据传输效率和应用程序性能。
  2. ChannelHandlerContext对象池:Netty中的ChannelHandlerContext代表了处理器(如编码 器、解码器、处理器等)与Channel之间的关联关系。通过使用ChannelHandlerContext对象池, Netty可以在数据处理过程中重用上下文对象,减少上下文对象的创建和销毁开销,从而提高数据 处理的效率。
  3. 资源回收和管理:Netty的对象池技术能够自动地管理对象的生命周期和资源回收。当对象不再需 要时,它们会被返回到对象池,以便稍后重用。这有助于避免内存泄漏和资源浪费。
  4. 配置和定制:Netty允许开发者根据应用程序的需求进行对象池的配置和定制。开发者可以设置池 的大小、对象的生存时间等参数,以适应不同的场景和负载。 综上所述,Netty的对象池技术是一项重要的功能,能够有效地提高网络应用程序的内存使用效率和性 能,特别是在处理大规模数据和高并发情况下。通过重用对象,Netty能够降低资源开销,提高数据传输 效率,并且在一定程度上减少内存碎片化问题。

9、Netty有哪些序列化协议?

Netty并不直接提供序列化协议,但它可以与各种序列化协议进行集成。序列化是将对象转换为可在网络 上传输或持久化存储的格式的过程。Netty可以与多种序列化协议一起使用,以根据应用程序的需要进行 数据的编码和解码。以下是一些常见的序列化协议,可以与Netty一起使用:

  1. Java自带的序列化(Java Serialization):Java自带了一套对象序列化机制,可以将Java对象转 换为字节流进行传输。但是,这种序列化方式在性能和灵活性方面可能存在问题,因此在高性能网 络应用中可能不是首选。
  2. JSON(JavaScript Object Notation):JSON是一种轻量级的数据交换格式,易于阅读和编写, 适用于各种编程语言。Netty可以与JSON库(如Jackson、Gson等)一起使用,将对象转换为JSON 格式进行传输。
  3. Protobuf(Protocol Buffers):Protobuf是一种由Google开发的高效的二进制序列化协议,具 有很高的性能和紧凑的数据表示。Netty可以与Protobuf集成,使用Protobuf生成的类来进行对象 的编码和解码。
  4. MessagePack:MessagePack是一种基于二进制的轻量级序列化格式,具有高性能和紧凑的数据 表示。它可以与Netty一起使用,实现数据的传输和解析。
  5. Thrift:Thrift是由Apache开发的一种跨语言的序列化协议,支持多种编程语言,并具有高性能和 可扩展性。Netty可以与Thrift一起使用,实现对象的序列化和反序列化。
  6. Avro:Avro是另一种由Apache开发的序列化框架,旨在提供紧凑的二进制格式和动态数据模型。 Netty可以与Avro一起使用,实现数据的编码和解码。 这些序列化协议可以根据应用程序的需求进行选择,根据性能、数据大小、跨语言支持等因素来决定使 用哪种协议。Netty的灵活性使得它能够与各种序列化协议集成,以实现高效的网络通信。

10、Netty 中用了哪些设计模式?

在Netty中使用了许多设计模式来实现高效的网络通信和处理。以下是一些Netty中使用的设计模式:

  1. 工厂模式(Factory Pattern):Netty使用工厂模式来创建不同类型的通道、处理器和其他组 件,隐藏了对象创建的细节,使代码更具可维护性和扩展性。
  2. 装饰器模式(Decorator Pattern):Netty的处理器链(Pipeline)机制使用了装饰器模式。每个 处理器都可以在收到数据、处理数据和传递数据时添加额外的逻辑,这使得用户可以轻松地定制数 据的处理流程。
  3. 观察者模式(Observer Pattern):Netty中的事件和事件监听器机制使用了观察者模式。通道状 态变化、数据读写等事件可以被观察,而用户可以注册相应的监听器来处理这些事件。
  4. 责任链模式(Chain of Responsibility Pattern):Netty的处理器链(Pipeline)本质上就是一 个责任链,每个处理器负责特定的任务,可以在链中按顺序处理数据,将复杂的处理逻辑拆分成独 立的模块。
  5. 单例模式(Singleton Pattern):Netty中的一些关键组件,如线程池、事件循环,都使用了单例 模式确保只有一个实例存在,从而节省资源并确保一致性。
  6. 模板方法模式(Template Method Pattern):Netty的一些类提供了模板方法,定义了通用的 处理流程和步骤,而将具体的实现细节留给子类来实现。
  7. 策略模式(Strategy Pattern):Netty中的一些组件,如编码器和解码器,可以根据不同的业务 需求进行替换,这种灵活性符合策略模式的思想。
  8. 适配器模式(Adapter Pattern):Netty中的适配器可以帮助用户将不同的数据格式、协议等转 换成统一的格式,以适应不同的通信需求。 这些设计模式的使用使得Netty能够提供高度灵活、高性能和可扩展的网络通信框架,满足各种不同应用 场景的需求。

2、Tomcat

1、Tomcat的缺省端口是多少,怎么修改?

默认端口为8080,可以通过在tomcat安装包conf目录下,service.xml中的Connector元素的port属性 来修改端口。

2、Tomcat有哪几种Connector 运行模式(优化)?

这三种模式的不同之处如下: BIO:一个线程处理一个请求。缺点:并发量高时,线程数较多,浪费资源。Tomcat7版本或更低版本 中,在Linux系统中默认使用这种方式。 NIO:利用Java的异步IO处理,可以通过少量的线程处理大量的请求。tomcat8.0.x中默认使用的是 NIO。Tomcat7必须修改Connector配置来启动: APR:即Apache Portable Runtime,从操作系统层面解决io阻塞问题。Tomcat7或Tomcat8在Win7或 以上的系统中启动默认使用这种方式。

3、Tomcat有几种部署方式?

利用Tomcat的自动部署:把web应用拷贝到webapps目录(生产环境不建议放在该目录中)。Tomcat 在启动时会加载目录下的应用,并将编译后的结果放入work目录下。 使用Manager App控制台部署:在tomcat主页点击“Manager App” 进入应用管理控制台,可以指定一 个web应用的路径或war文件。修改conf/server.xml文件部署:在server.xml文件中,增加Context节点 可以部署应用。 增加自定义的Web部署文件:在conf/Catalina/localhost/路径下增加 xyz.xml文件,内容是Context节 点,可以部署应用。

4、Tomcat容器是如何创建Servlet类实例?用到了什么原理?

当容器启动时,会读取在webapps目录下所有的web应用中的web.xml文件,然后对 xml文件进行解 析,并读取servlet注册信息。然后,将每个应用中注册的servlet类都进行加载,并通过反射的方式实例 化。(有时候也是在第一次请求时实例化)在servlet注册时加上1如果为正数,则在一开始就实例化, 如果不写或为负数,则第一次请求实例化。

<Connector port="8080" protocol="org.apache.coyote.http11.Http11NioProtocol"
connectionTimeout="20000" redirectPort="8443"/>

5、Tomcat如何优化?

tomcat作为Web服务器,它的处理性能直接关系到用户体验,下面是几种常见的优化措施: 掉对web.xml的监视,把jsp提前编辑成Servlet。有富余物理内存的情况,加大tomcat使用的jvm的内存 服务器所能提供CPU、内存、硬盘的性能对处理能力有决定性影响。 对于高并发情况下会有大量的运算,那么CPU的速度会直接影响到处理速度。内存在大量数据处理的情 况下,将会有较大的内存容量需求,可以用-Xmx -Xms -XX:MaxPermSize等参数对内存不同功能块进行 划分。我们之前就遇到过内存分配不足,导致虚拟机一直处于full GC,从而导致处理能力严重下降。硬 盘主要问题就是读写性能,当大量文件进行读写时,磁盘极容易成为性能瓶颈。最好的办法还是利用下 面提到的缓存。利用缓存和压缩 对于静态页面最好是能够缓存起来,这样就不必每次从磁盘上读。这里我们采用了Nginx作为缓存服务 器,将图片、css、js文件都进行了缓存,有效地减少了后端tomcat的访问。另外,为了能加快网络传输 速度,开启gzip压缩也是必不可少的。但考虑到tomcat已经需要处理很多东西了,所以把这个压缩的工 作就交给前端的Nginx来完成。除了文本可以用gzip压缩,其实很多图片也可以用图像处理工具预先进行 压缩,找到一个平衡点可以让画质损失很小而文件可以减小很多。曾经我就见过一个图片从300多kb压 缩到几十kb,自己几乎看不出来区别。采用集群 单个服务器性能总是有限的,最好的办法自然是实现横向扩展,那么组建tomcat集群是有效提升性能的 手段。我们还是采用了Nginx来作为请求分流的服务器,后端多个tomcat共享session来协同工作。 优化线程数优化 找到Connector port=“8080” protocol=“HTTP/1.1”,增加maxThreads和acceptCount属性(使 acceptCount大于等于maxThreads),如下: 其中: maxThreads:tomcat可用于请求处理的最大线程数,默认是200 minSpareThreads:tomcat初始线程 数,即最小空闲线程数 maxSpareThreads:tomcat最大空闲线程数,超过的会被关闭 acceptCount: 当所有可以使用的处理请求的线程数都被使用时,可以放到处理队列中的请求数,超过这个数的请求将 不予处理.默认100 使用线程池优化 在server.xml中增加executor节点,然后配置connector的executor属性,如下: 其中: namePrefix:线程池中线程的命名前缀 maxThreads:线程池的最大线程数 minSpareThreads:线程 池的最小空闲线程数 maxIdleTime:超过最小空闲线程数时,多的线程会等待这个时间长度,然后关闭 threadPriority:线程优先级 注:当tomcat并发用户量大的时候,单个jvm进程确实可能打开过多的文件句柄,这时会报 java.net.SocketException:Too many open files错误。可使用下面步骤检查:

<Connector port="8080" protocol="HTTP/1.1"connectionTimeout="20000"
redirectPort="8443"acceptCount="500" maxThreads="400" />
<Executor name="tomcatThreadPool" namePrefix="req-exec-"maxThreads="1000"
minSpareThreads="50"maxIdleTime="60000"/><Connector port="8080"
protocol="HTTP/1.1"executor="tomcatThreadPool"/>

ps -ef |grep Tomcat查看tomcat的进程ID,记录ID号,假设进程ID为10001 lsof -p 10001|wc -l 查看当 前进程id为10001的文件操作数使用命令:ulimit -a 查看每个用户允许打开的最大文件数 启动速度优化 删除没用的web应用:因为tomcat启动每次都会部署这些应用。关闭WebSocket:websocket-api.jar和 tomcat-websocket.jar。随机数优化:设置JVM参数:-Djava.security.egd=file:/dev/./urandom。内存 优化 因为tomcat启动起来后就是一个java进程,所以这块可以参照JVM部分的优化思路。堆内存相关参数, 比如说: -Xms:虚拟机初始化时的最小堆内存。 -Xmx:虚拟机可使用的最大堆内存。-Xms与-Xmx设成一样的值,避免JVM因为频繁的GC导致性能大起 大落 -XX:MaxNewSize:新生代占整个堆内存的最大值。 另外还有方法区参数调整(注意:JDK版本)、垃圾收集器等优化。JVM相关参数请看:手把手教你设置 JVM调优参数

6、熟悉Tomcat的哪些配置?

Context(表示一个web应用程序,通常为WAR文件,关于WAR的具体信息见servlet规范)标签。 docBase:该web应用的文档基准目录(Document Base,也称为Context Root),或者是WAR文件的 路径。可以使绝对路径,也可以使用相对于context所属的Host的appBase路径。 path:表示此web应用程序的url的前缀,这样请求的url为http://localhost:8080/path/。 reloadable:这个属性非常重要,如果为true,则tomcat会自动检测应用程序的/WEB-INF/lib和/WEB- INF/classes目录的变化,自动装载新的应用程序,我们可以在不重启tomcat的情况下改变应用程序。 useNaming:如果希望Catalina为该web应用使用一个JNDI InitialContext对象,设为true。该 InitialialContext符合J2EE平台的约定,缺省值为true。 workDir:Context提供的临时目录的路径,用于servlet的临时读/写。利用 javax.servlet.context.tempdir属性,servlet可以访问该目录。如果没有指定,使用 $CATALINA_HOME/work下一个合适的目录。 swallowOutput:如果该值为true,System.out和System.err的输出被重定向到web应用的logger。如 果没有指定,缺省值为false debug:与这个Engine关联的Logger记录的调试信息的详细程度。数字越大,输出越详细。如果没有指 定,缺省为0。 host(表示一个虚拟主机)标签。 name:指定主机名。 appBase:应用程序基本目录,即存放应用程序的目录。 unpackWARs:如果为true,则tomcat会自动将WAR文件解压,否则不解压,直接从WAR文件中运行应 用程序。 Logger(表示日志,调试和错误信息)标签。 className:指定logger使用的类名,此类必须实现org.apache.catalina.Logger接口。 prefix:指定log文件的前缀。 suffix:指定log文件的后缀。 timestamp:如果为true,则log文件名中要加入时间,如下例:localhost_log.2001-10-04.txt。

3、计算机网络

1、介绍一下OSI七层模型?

当然,我很乐意为您介绍一下OSI七层模型。 OSI七层模型是一个用于描述计算机网络通信协议的框架,它由国际标准化组织(ISO)于1984年制定并 发布。该模型将网络通信过程分解为七个不同的层次,每个层次都负责特定的功能,从物理连接到应用 程序的交互,以实现数据在不同设备之间的传输和交换。以下是每个层次的简要介绍:

  1. 物理层(Physical Layer):这是最底层的层次,负责处理物理媒介和电信号传输。它定义了连 接硬件设备的标准,例如电缆类型、电压规范等。
  2. 数据链路层(Data Link Layer):此层负责在直接相连的两个设备之间传输数据帧,通过物理地 址(MAC地址)进行寻址和识别。它还处理错误检测和校正,确保可靠的数据传输。
  3. 网络层(Network Layer):网络层负责在不同网络之间路由数据包,通过IP地址进行寻址。它 处理数据包的传输路径选择和逻辑寻址,以实现跨网络的通信。
  4. 传输层(Transport Layer):传输层负责在端到端的通信中提供数据传输的可靠性和错误检测。 它管理数据的分段、传输控制、流量控制等,常见的传输层协议包括TCP(传输控制协议)和UDP (用户数据报协议)。
  5. 会话层(Session Layer):会话层负责建立、管理和终止应用程序之间的会话连接。它处理会话 的控制和同步,确保数据正确传输并在需要时进行恢复。
  6. 表示层(Presentation Layer):表示层负责数据的格式化、加密、压缩等处理,以确保不同系 统间数据的交换和解释能够无障碍地进行。
  7. 应用层(Application Layer):应用层是最顶层的层次,直接为用户提供网络服务和应用程序。 它包含了各种应用,如电子邮件、文件传输、远程访问等。 通过这种分层的方式,OSI模型帮助网络工程师和开发者更好地理解和设计网络协议、通信和应用。然 而,需要注意的是,实际网络协议不一定都严格按照这七层模型来设计,而是根据实际需求和技术进行 了调整和扩展。

2、什么是TCP的粘包、拆包

TCP(Transmission Control Protocol)是一种常用的传输层协议,用于在计算机网络上可靠地传输数 据。在TCP通信中,粘包(Packet Sticking)和拆包(Packet Splitting)是两个可能出现的问题,涉及 到数据的传输和接收过程中的数据分割和组合。 粘包(Packet Sticking):粘包指的是在发送端将多个小数据包连续发送,而接收端却可能一次性接 收到了多个小数据包,这些数据包“粘”在一起,无法准确分辨出每个数据包的界限。这可能是因为发送 端的数据写入缓冲区比较快,而接收端读取数据的速度较慢,导致多个数据包被一次性读取。粘包会导 致接收端无法正确解析数据,从而影响数据的处理和解释。 拆包(Packet Splitting):拆包指的是在发送端将一个大数据包分割成多个小数据包发送,而接收端 却可能无法正确地将这些小数据包组合成完整的大数据包。这可能是因为发送端的数据分割不当,或者 接收端没有足够的信息来确定如何正确组合这些小数据包。拆包会导致接收端无法还原原始数据,从而 造成数据的错误或丢失。 为了解决粘包和拆包问题,通常需要在应用层设计一些协议或者采用一些技术手段,例如:

  1. 消息长度字段:在消息的开头加入一个固定长度的字段,用来表示后续消息的长度,接收端可以根 据这个字段来准确地分割和组合数据包。
  2. 消息分隔符:在消息的末尾加入一个特定的分隔符,接收端通过识别分隔符来分割数据包。
  3. 使用固定长度消息:将所有消息都固定到同样的长度,不足的部分用填充数据补齐。
  4. 应用层协议设计:在应用层定义明确的消息格式和协议,确保发送端和接收端都按照相同的规则进 行数据的分割和组合。 总之,处理粘包和拆包问题需要在应用层进行合适的设计和处理,以确保数据在传输过程中能够正确地 分割和组合,保证通信的可靠性和准确性。

3、ARP 与 RARP 的区别是什么?

ARP(Address Resolution Protocol)和RARP(Reverse Address Resolution Protocol)都是用于在网 络通信中解决IP地址和物理MAC地址之间映射关系的协议,但它们的功能和应用场景有所不同。

  1. ARP(Address Resolution Protocol): ARP用于将一个已知的IP地址解析成对应的物理MAC地 址。在一个局域网中,当主机需要与另一个主机通信时,它会先检查自己的ARP缓存表,看是否已 经有目标IP地址对应的MAC地址。如果没有,它就会发送一个ARP请求广播,询问局域网内是否有 响应该IP地址的主机。目标主机收到该请求后,会发送一个ARP响应,包含自己的MAC地址。这 样,请求主机就可以得到目标主机的MAC地址,从而建立通信。
  2. RARP(Reverse Address Resolution Protocol): RARP与ARP相反,它用于将已知的物理 MAC地址解析成对应的IP地址。主要应用于无盘工作站等设备,在启动时需要获取自己的IP地址, 但是这些设备没有预设IP地址,只有MAC地址。设备启动时会发送一个RARP请求广播,请求分配 一个IP地址。RARP服务器会收到请求后,查找MAC地址对应的IP地址,然后将IP地址发送回设备, 使设备能够配置自己的IP地址。 总结区别: ARP解析IP地址到MAC地址,而RARP解析MAC地址到IP地址。 ARP主要用于常规网络通信,而RARP主要用于无盘设备等特殊情况下的地址配置。 ARP请求由需要通信的主机发出,而RARP请求由需要获取IP地址的设备发出。 需要注意的是,随着网络技术的发展,ARP和RARP在现代网络中的使用逐渐减少,因为有更高级的技术 和协议来管理IP地址和MAC地址的映射关系,如DHCP(Dynamic Host Configuration Protocol)和 IPv6的邻居发现协议。

4、路由器与交换机的区别是什么?

路由器(Router)和交换机(Switch)是网络中常见的两种设备,它们在网络中扮演不同的角色,有以 下区别:

  1. 功能与工作层次: 路由器:路由器位于网络的边缘,连接不同的网络或子网,主要负责在不同网络之间转发数据 包。它能够基于目标IP地址决定数据包的最佳路径,从而实现网络之间的通信。 交换机:交换机通常位于局域网(LAN)内部,它通过学习MAC地址来建立和维护一个MAC 地址表,然后根据MAC地址表将数据包直接从源端口转发到目标端口,从而实现局域网内部 设备之间的通信。
  2. 转发决策: 路由器:路由器通过查看数据包的目标IP地址来进行转发决策,选择最佳路径将数据包从一个 网络传送到另一个网络。 交换机:交换机通过查看数据包的源MAC地址来决定将数据包发送到哪个目标端口,以实现 局域网内部设备之间的直接通信。
  3. 广播域和碰撞域: 路由器:路由器能够隔离不同网络之间的广播域和碰撞域,这意味着在不同网络间的广播消息 不会被传播到其他网络。 交换机:交换机也能够隔离广播域,但在同一个交换机内部,所有设备共享一个碰撞域,因此 在交换机内部通信不会发生碰撞。
  4. 网络范围: 路由器:路由器通常用于连接不同的网络,可以跨越不同的物理位置和地理区域。 交换机:交换机通常用于连接同一局域网内的设备,限制在一个相对较小的网络范围内。 总之,路由器和交换机在网络中扮演不同的角色,分别用于实现不同的通信需求。路由器负责不同网络 之间的数据包转发,而交换机则负责同一网络内部设备之间的数据包交换。

5、什么是TCP三次握手、四次挥手?

当然,我可以解释一下TCP三次握手和四次挥手的概念。 TCP三次握手: TCP三次握手是在建立TCP连接时使用的一种协议,用于确保客户端和服务器之间的通信能够稳定地开 始。这个过程涉及三个步骤:

  1. 第一步(SYN):客户端向服务器发送一个带有SYN(同步)标志的数据包,请求建立连接。客户 端进入SYN_SENT状态。
  2. 第二步(SYN-ACK):服务器收到客户端的请求后,会发送一个带有SYN和ACK(确认)标志的数 据包,表示同意建立连接。服务器进入SYN_RECEIVED状态。
  3. 第三步(ACK):客户端收到服务器的响应后,发送一个带有ACK标志的数据包给服务器,确认连 接已建立。双方都进入已建立连接的状态,可以开始进行数据传输。 这三步握手过程确保了双方都同意建立连接,并且双方都准备好了进行数据传输。 TCP四次挥手: TCP四次挥手是在关闭TCP连接时使用的一种协议,用于确保双方能够安全地终止连接。这个过程涉及四 个步骤:
  4. 第一步(FIN):一方(通常是客户端)向另一方(通常是服务器)发送一个带有FIN(结束)标志 的数据包,表示希望关闭连接。发送方进入FIN_WAIT_1状态。
  5. 第二步(ACK):接收方收到关闭请求后,发送一个带有ACK标志的数据包作为确认。发送方进入 FIN_WAIT_2状态,等待接收方的确认。
  6. 第三步(FIN):接收方准备好关闭连接时,会发送一个带有FIN标志的数据包,表示同意关闭连 接。接收方进入CLOSE_WAIT状态。
  7. 第四步(ACK):发送方收到接收方的关闭确认后,发送一个带有ACK标志的数据包作为确认。双 方都进入连接已关闭的状态。 这四步挥手过程确保双方都完成了数据传输,并且同意关闭连接,避免了数据丢失或不完整的情况。 总之,TCP三次握手和四次挥手是确保通信的可靠性和完整性的重要步骤,分别用于建立和关闭TCP连 接。

6、TCP是如何保证可靠传输的?

TCP(传输控制协议)保证可靠传输的主要方式包括以下几个方面:

  1. 三次握手:在建立连接时,发送方首先发送一个SYN(同步)标志的数据包给接收方。接收方收到 后,确认收到这个SYN,并发送一个带有ACK(确认)和SYN标志的数据包给发送方。最后,发送 方再发送一个带有ACK标志的数据包作为确认。这三步握手确保双方都同意建立连接,减少了连接 错误的可能性。
  2. 序列号和确认机制:TCP使用序列号来标识每个发送的数据包,接收方根据序列号确认收到的数据 包。如果发送方未收到确认,会重新发送数据包,直到接收到确认为止。这保证了数据的可靠传 输,避免了数据包丢失的情况。
  3. 数据分段和重组:TCP将应用层传输的大块数据分割成小的数据段进行传输,接收方根据序列号将 这些数据段重新组装成完整的数据。如果发现某个数据段丢失,TCP会要求重新发送该数据段,确 保数据的完整性。
  4. 流量控制:TCP利用滑动窗口机制进行流量控制,确保发送方不会发送过多的数据导致接收方无法 及时处理。这防止了拥塞并保持了传输的平稳。
  5. 超时重传:如果发送方在一定时间内没有收到确认,会认为数据包丢失,然后会重新发送这些数据 包。这样即使数据包在传输过程中丢失,也能够通过超时重传来确保数据的到达。
  6. 四次挥手:在关闭连接时,发送方和接收方都要确认关闭,防止数据的丢失或不完整。 通过上述机制,TCP可以有效地保证数据的可靠传输,确保数据在传输过程中不会丢失、重复或无序。

7、基于UDP实现一个TCP协议

UDP(User Datagram Protocol)是一种无连接的、不可靠的传输协议,而TCP(Transmission Control Protocol)是一种面向连接的、可靠的传输协议。基于UDP实现一个TCP协议是不可行的,因为 UDP缺乏TCP提供的许多关键特性,如可靠的数据传输、流量控制、拥塞控制等。 然而,你可以使用UDP来实现一种类似TCP的协议,提供一些基本的可靠性和有序性。下面是一个简单 的示例,展示了如何使用UDP实现一个基于可靠传输的简化TCP协议:

  1. 序号和确认机制:在数据包中添加序号字段和确认字段。发送方将每个数据包分配一个唯一的序 号,并等待接收方发送确认消息。接收方收到数据包后,发送确认消息,确认收到的最后一个有序 数据包的序号。发送方根据接收到的确认消息确定哪些数据包已经被成功接收。
  2. 超时重传:发送方需要设置一个定时器,在发送数据包后启动计时器。如果在一定时间内没有收到 确认消息,发送方会假设数据包丢失,并重新发送该数据包。接收方在收到重复的数据包时可以丢 弃重复的数据。
  3. 流量控制:发送方和接收方可以使用滑动窗口机制来进行流量控制。发送方根据接收方的可接受窗 口大小来确定发送的数据量,并根据接收方发送的确认消息调整发送窗口的大小。
  4. 拥塞控制:由于UDP本身不提供拥塞控制机制,你可以基于UDP实现一些简单的拥塞控制策略, 如慢启动和拥塞避免。这可以包括动态调整发送窗口大小和控制发送速率,以避免网络拥塞。 需要注意的是,尽管你可以使用UDP实现一些类似TCP的特性,但由于UDP的本质特点,它仍然无法提 供与TCP完全相同的可靠性和性能。在实际应用中,如果需要可靠的数据传输和其他高级特性,建议使 用TCP协议而不是基于UDP的自定义协议。

8、为什么需要HTTP/2,他解决了什么问题?

HTTP/2 是 HTTP 协议的下一代版本,旨在改进性能和效率。它解决了 HTTP/1.1 存在的一些问题,并引 入了一些新的特性和优化,以提供更快的网页加载速度、更高的效率和更好的用户体验。以下是 HTTP/2 解决的一些问题:

  1. 多路复用(Multiplexing):在 HTTP/1.1 中,每个请求都需要使用单独的连接,导致了高延迟和 低效率。HTTP/2 使用二进制分帧层,通过在单个连接上同时发送多个请求和响应,实现了多路复 用。这意味着可以并发处理多个请求,减少了延迟,提高了效率。
  2. 头部压缩(Header Compression):在 HTTP/1.1 中,每个请求和响应的头部都需要重复发 送,浪费了带宽和资源。HTTP/2 引入了头部压缩机制,使用了 HPACK 压缩算法,可以显著减少 头部的大小,减少了数据传输量,提高了效率。
  3. 服务器推送(Server Push):在 HTTP/1.1 中,客户端需要发送多个请求来获取网页中的所有资 源,例如脚本、样式表和图片等。而 HTTP/2 支持服务器推送,服务器可以主动将与请求的资源相 关的其他资源一起推送给客户端,减少了额外的请求延迟,提高了页面加载速度。
  4. 流量控制(Flow Control): HTTP/2 引入了流量控制机制,可以对数据流进行控制,防止发送方 发送过多的数据导致接收方无法处理。这有助于平衡发送和接收之间的速度差异,提高了性能和稳 定性。
  5. 优先级(Priority): HTTP/2 支持请求的优先级设置,可以告知服务器哪些请求更重要,服务器 可以相应地优先处理重要的请求,提高了用户体验。 总体而言,HTTP/2 在性能、效率和用户体验方面有了显著的改进。它通过多路复用、头部压缩、服务 器推送、流量控制和优先级等特性,减少了延迟、提高了吞吐量,加快了网页加载速度,提供了更好的 性能和效率。

9、HTTP/2存在什么问题,为什么需要HTTP/3?

尽管 HTTP/2 在性能和效率方面带来了显著的改进,但它仍然存在一些问题,这些问题促使了 HTTP/3 的出现。以下是一些 HTTP/2 存在的问题:

  1. 依赖于 TCP 协议: HTTP/2 是在 TCP 协议之上构建的,而 TCP 协议在高延迟和不可靠的网络环境 下存在一些问题。当出现丢包或网络拥塞时,TCP 使用的拥塞控制机制会导致连接的延迟增加,从 而影响性能。
  2. 队头阻塞(Head-of-Line Blocking): HTTP/2 使用多路复用技术,但在一个连接上的多个请求 和响应共享同一个传输通道。如果其中一个请求或响应出现延迟或丢失,将会阻塞后续的请求和响 应,这被称为队头阻塞。这可能导致其他请求受到影响,影响了整体性能。
  3. 部署复杂性: HTTP/2 的实施相对复杂,需要对服务器和客户端进行更新以支持新的协议。这可能 导致一些兼容性和部署问题,使得采用 HTTP/2 变得相对困难。 为了解决这些问题,HTTP/3 应运而生,它采用了全新的传输协议——QUIC(Quick UDP Internet Connections)。HTTP/3 基于 UDP 协议,而不是 TCP,以提供更好的性能和可靠性。以下是为什么需 要 HTTP/3 的一些原因:
  4. 解决 TCP 的问题: QUIC 作为传输层协议,克服了 TCP 的一些问题,如连接建立的延迟、队头阻 塞和拥塞控制机制。QUIC 使用自己的拥塞控制算法,可以更快地适应网络状况的变化,并提供更 好的性能。
  5. 减少延迟: HTTP/3 使用 QUIC 协议,通过减少握手时间和降低队头阻塞的影响,可以显著减少延 迟。这对于实时通信、视频流和移动应用等对低延迟要求较高的场景非常重要。
  6. 更好的并发性: HTTP/3 在一个连接上可以同时处理多个请求和响应,避免了队头阻塞的问题。这 提供了更好的并发性和吞吐量,提高了性能。
  7. 更好的适应性: HTTP/3 的部署相对简单,因为它使用了现有的 UDP 协议。它可以更容易地通过 NAT 和防火墙,适应多样化的网络环境。 总的来说,HTTP/3 通过引入 QUIC 协议,解决了 HTTP/2 存在的一些问题,如 TCP 的限制和队头阻 塞。它提供了更好的性能、更低的延迟和更好的并发性,同时具有更好的适应性和部署简单性。

10、Cookie,Session,Token的区别是什么?

Cookie、Session 和 Token 都是用于在 Web 应用程序中管理用户身份验证和状态的方式,但它们在实 现和使用上有一些区别。以下是它们之间的主要区别:

  1. Cookie: Cookie 是一小段文本信息,由服务器发送到用户的浏览器,并存储在用户的本地计算机上。 主要用于在浏览器和服务器之间存储少量数据,通常用于身份验证、用户偏好设置、跟踪用户 活动等。 Cookie 存储在客户端,可以在浏览器中设置过期时间,以及作用域(可以限定在特定的域名 或路径下)。 可以被浏览器禁用或删除,且可能存在安全性问题,如跨站点脚本(XSS)攻击和跨站请求伪 造(CSRF)攻击。
  2. Session: Session 是服务器端存储的用户信息,通过一个唯一的会话标识(Session ID)与客户端进行 关联。 当用户访问服务器时,服务器会创建一个新的 Session,并分配一个唯一的 Session ID,将用 户数据存储在服务器上,而不是在用户的浏览器中。 Session 数据存储在服务器上,通常在内存、数据库或缓存中,因此相对安全,但会占用服务 器资源。 Session 常用于存储敏感信息,如用户身份验证状态、购物车内容等。
  3. Token: Token 是一种令牌,通常是一个加密的字符串,用于验证用户身份和授权。 在基于令牌的身份验证中,用户在登录后会收到一个令牌,将其保存在客户端(通常是本地存 储或 Cookie)中,并在每次请求时将令牌发送给服务器。 服务器使用密钥验证令牌的有效性,从而确定用户身份和权限。 令牌可以在客户端存储,不需要在服务器上维护会话状态,因此适用于分布式系统和无状态应 用。 总结起来: Cookie 是在客户端存储的小段文本数据,通常用于存储用户偏好和跟踪用户活动。 Session 是服务器端存储的用户数据,通过唯一的会话标识与客户端关联,常用于存储敏感信息。 Token 是一种令牌,用于验证用户身份和授权,适用于无状态应用和分布式系统。 选择使用哪种方法取决于应用程序的需求和安全性考虑。

11、HTTPS只是比HTTP安全吗?

HTTPS(Hypertext Transfer Protocol Secure)不仅仅是比 HTTP(Hypertext Transfer Protocol)更 安全,而且它提供了一系列安全性和保护机制,以确保在 Web 通信中的数据保密性、完整性和身份验 证。以下是 HTTPS 相对于 HTTP 的主要安全改进:

  1. 数据加密: HTTPS 使用 SSL(Secure Sockets Layer)或 TLS(Transport Layer Security)协议 对传输的数据进行加密。这意味着在数据从客户端发送到服务器的过程中,第三方无法轻易地截 取、窃听或篡改数据。加密保护了用户的敏感信息,如登录凭据、支付信息等。
  2. 身份验证: HTTPS 通过使用数字证书对服务器进行身份验证。数字证书由受信任的证书颁发机构 (Certificate Authority,CA)签发,用于证明服务器的身份。这样,用户可以确信他们正在与合 法的服务器通信,而不是被冒充的恶意服务器。
  3. 数据完整性: HTTPS 使用加密算法和消息认证码(Message Authentication Code,MAC)来确 保数据在传输过程中没有被篡改或损坏。接收方可以验证数据的完整性,以确保数据的原始性和完 整性。
  4. 信任度和安全指示:浏览器在使用 HTTPS 连接时会显示安全指示,如绿色锁图标、网站名称旁边 的“安全”标签等。这些指示向用户传达了对网站的信任和数据的保护,帮助用户识别安全的网站并 减少受到网络攻击的风险。
  5. 防止窃听和篡改: HTTPS 的加密机制防止了中间人攻击(Man-in-the-Middle Attack),其中攻 击者可以窃听、篡改或伪造通信内容。通过加密和身份验证,HTTPS 提供了更高的安全性,使得中 间人攻击变得更加困难。 总而言之,HTTPS 不仅仅是比 HTTP 更安全,它通过加密通信、身份验证、数据完整性和安全指示等机 制,提供了更高级别的保护,确保用户的数据在传输过程中得到保密、完整和安全。因此,在涉及敏感 信息传输的场景中,使用 HTTPS 是非常重要和推荐的。

12、浏览器输入www.baidu.com回车之后发生了什么

当在浏览器中输入 “[www.baidu.com]” 并按下回车之后,以下是大致的步骤和过程:

  1. 域名解析:浏览器首先会将 “[www.baidu.com]” 解析为 IP 地址。它会检查本地 DNS 缓存,如果 找到了对应的 IP 地址,则跳过后续步骤。如果没有找到,则浏览器会向本地计算机的 DNS 解析器 发送查询请求。
  2. DNS 查询:本地计算机的 DNS 解析器会收到浏览器发送的 DNS 查询请求,并尝试解析域名 “[ww w.baidu.com”。如果本地解析器缓存中有对应的记录,则返回解析结果给浏览器。如果没有缓存 记录,则本地解析器会向根域名服务器发送查询请求。
  3. 递归查询:根域名服务器收到查询请求后,会返回给本地解析器一个对应的顶级域名服务器的 IP 地址。本地解析器再次向顶级域名服务器发送查询请求。
  4. 迭代查询:顶级域名服务器收到查询请求后,会返回给本地解析器一个次级域名服务器的 IP 地 址。本地解析器再次向次级域名服务器发送查询请求。
  5. 迭代查询继续:这个过程会一直进行下去,直到本地解析器获得了最终的目标服务器的 IP 地址。
  6. 建立 TCP 连接:一旦浏览器获得了目标服务器的 IP 地址,它会使用 HTTP 协议中的默认端口 (80)或 HTTPS 协议中的默认端口(443)与服务器建立 TCP 连接。
  7. 发送 HTTP 请求:浏览器通过建立的 TCP 连接向服务器发送一个 HTTP 请求。这个请求包含了请 求的方法(如 GET、POST)、请求的路径(如 ”/“)以及其他的头部信息(如用户代理、Cookie 等)。
  8. 服务器处理请求:服务器收到浏览器发送的请求后,会根据请求的路径和其他信息来处理请求。对 于百度的首页请求,服务器会返回相应的 HTML 页面。
  9. 服务器响应:服务器处理完请求后,会生成一个 HTTP 响应。响应包括响应的状态码(如 200 表 示成功)、响应的内容(如 HTML 页面)以及其他的头部信息(如响应的日期、内容类型等)。
  10. 接收和渲染页面:浏览器收到服务器的响应后,会解析响应内容并渲染页面。它会将 HTML 解析 为 DOM(文档对象模型),加载和显示页面中的其他资源(如 CSS、JavaScript、图像等)。
  11. 断开连接:页面渲染完成后,浏览器和服务器之间的 TCP 连接会被断开。 以上是一个简化的描述,实际的过程可能还涉及到其他的细节和步骤。但总体上,这些步骤描述了当在 浏览器中输入 “www.baidu.com” 并按下回车后,浏览器如何解析域名、建立连接、发送请求、接收响 应和渲染页面的过程。

13、对称加密和非对称加密有什么区别?

对称加密和非对称加密是两种常见的加密算法,它们在加密和解密数据时有一些重要的区别:

对称加密:

使用相同的密钥(称为密钥)来进行加密和解密数据。 加密和解密的过程速度较快,因为使用的算法相对简单。 对称加密适用于大量数据的加密,如文件传输。 密钥的管理相对较为复杂,需要确保密钥的安全性,防止未授权的人获取密钥。 常见的对称加密算法有 DES(Data Encryption Standard)、AES(Advanced Encryption Standard)等。

非对称加密:

使用一对密钥,分别是公钥和私钥。公钥用于加密数据,私钥用于解密数据。 加密和解密的过程相对较慢,因为使用的算法相对复杂。 非对称加密适用于安全性要求较高的场景,如身份验证、数字签名等。 公钥可以公开分发,而私钥必须保密保存。 常见的非对称加密算法有 RSA(Rivest-Shamir-Adleman)、DSA(Digital Signature Algorithm)等。 总结起来,对称加密使用相同的密钥进行加密和解密,速度快但密钥管理复杂;非对称加密使用不同的 密钥进行加密和解密,安全性高但速度较慢。通常的做法是,对称加密用于加密大量的数据,而非对称 加密用于安全性要求较高的场景,例如建立安全的通信渠道、数字签名等。在实际应用中,通常会将对 称加密和非对称加密结合起来使用,以兼顾效率和安全性。

14、简单介绍一下DNS?

DNS(Domain Name System,域名系统)是互联网中用于将人类可读的域名转换为计算机可理解的IP 地址的一种系统。它充当了互联网上的“电话簿”,使用户能够通过易于记忆的域名访问网站,而不必记 住复杂的IP地址。 以下是 DNS 的基本工作原理和组成部分:

  1. 域名结构:域名按照层次结构划分,从右到左逐级具有不同的层级。例如,” [www.example.com]” 中,顶级域是 “.com”,次级域是 “example”,子域是 “www”。
  2. 域名解析:当用户在浏览器中输入一个域名,比如 “www.example.com”,操作系统或浏览器会向 本地计算机的DNS 解析器发送查询请求,以获取该域名对应的IP地址。
  3. DNS解析过程:如果本地解析器的缓存中没有目标域名的IP地址,它会执行以下步骤: 递归查询:本地解析器向根域名服务器发送查询请求,根服务器会返回顶级域名服务器的IP 地址。 迭代查询:本地解析器继续向顶级域名服务器发送查询请求,获得次级域名服务器的IP地 址。 继续迭代:这个过程会一直持续下去,直到本地解析器获取了目标域名的IP地址。
  4. 响应缓存:本地解析器在解析域名后,会将结果缓存一段时间。这样,如果再次有相同域名的查 询,就可以直接从缓存中获取结果,加快访问速度。
  5. 记录类型: DNS查询可以返回多种类型的记录,包括: A记录:将域名映射到IPv4地址。 AAAA记录:将域名映射到IPv6地址。 CNAME记录:将域名指向另一个域名。 MX记录:指定邮件服务器的地址。 NS记录:指定管理特定区域的域名服务器。 DNS在互联网中起着至关重要的作用,它不仅使人们能够通过易于记忆的域名访问网站,还支持电子邮 件、域名注册等多种互联网服务。然而,由于其核心的分布式特性,DNS也需要关注安全性和性能方面 的问题。

15、ping的原理是什么?

Ping是一种常用的网络工具,用于测试主机之间的连通性和测量网络延迟。它基于ICMP(Internet Control Message Protocol,互联网控制报文协议)来实现。 以下是Ping的基本工作原理:

  1. 发送ICMP Echo请求:当用户在命令行中执行ping命令,并指定目标主机的IP地址或域名时,操作 系统会创建一个ICMP Echo请求报文。
  2. 封装ICMP报文:ICMP Echo请求报文包含一个特定的标识符和序列号,以及一些其他的控制信 息。操作系统将ICMP报文封装在IP数据包中,设置目标IP地址为目标主机的IP地址。
  3. 发送数据包:操作系统将封装好的IP数据包发送到本地网络接口,通过网络传输到目标主机。
  4. 目标主机的响应:目标主机接收到ICMP Echo请求后,会检查目标IP地址是否与自己匹配。如果匹 配,则生成一个ICMP Echo响应报文。
  5. 返回响应数据包:目标主机将ICMP Echo响应报文封装在IP数据包中,并将数据包发送回源主机的 IP地址。
  6. 源主机接收响应:源主机接收到ICMP Echo响应后,会检查标识符和序列号是否与发送的请求匹 配。如果匹配,则认为目标主机可达,并计算往返时间(Round-Trip Time,RTT)。 Ping的原理基于ICMP协议,它通过发送Echo请求并接收Echo响应来测试主机之间的连通性。Ping命令 通常用于诊断网络问题、测量网络延迟和检查主机的可达性。通过比较发送请求和接收响应之间的时间 差,可以估计网络的延迟情况。

16、什么是IPV6?和IPV4有什么区别?

IPv6(Internet Protocol version 6,互联网协议第6版)是互联网上的一种网络协议,用于为设备分配 唯一的IP地址以及进行数据包传输。它是IPv4(Internet Protocol version 4,互联网协议第4版)的继 任者。IPv6的引入主要是为了解决IPv4地址空间不足、支持更多的设备连接以及提供更好的网络性能和 安全性等问题。 主要的区别如下:

  1. 地址空间:最显著的区别是IPv6提供了远远超过IPv4的地址空间。IPv4使用32位地址,最多支持约 42亿个不同的IP地址,而IPv6采用128位地址,可支持的地址数量极其庞大,约为3.4 x 10^38个。 这解决了IPv4中地址短缺的问题,使每个设备都能够拥有唯一的IP地址。
  2. 地址表示: IPv6地址使用冒号分隔的8组16进制数表示,例如: 2001:0db8:85a3:0000:0000:8a2e:0370:7334。为了缩短表示,IPv6允许省略前导零,以及连续的 零块。IPv4则使用点分十进制表示,例如:192.168.1.1。
  3. 自动配置: IPv6支持更强大的自动地址配置机制,设备可以通过Router Advertisement(路由器 通告)协议自动获取IP地址和其他网络配置信息,从而简化了网络设置。
  4. 安全性: IPv6在设计时考虑了更多的安全性特性,包括内置的IPSec(Internet Protocol Security,互联网协议安全)支持,可以更轻松地实现网络通信的加密和认证。
  5. 移动性支持: IPv6更好地支持移动设备,有助于实现无缝漫游和移动IP地址的更改。
  6. 流量控制和质量服务: IPv6引入了更灵活和精细的流量控制和质量服务机制,有助于提供更稳定和 高效的网络传输。
  7. NAT(Network Address Translation):在IPv4中,由于地址短缺,常常需要使用NAT来将多 个设备共享单个公共IP地址。在IPv6中,地址数量充足,减少了对NAT的需求,有助于简化网络配 置。 尽管IPv6带来了许多优势,但由于网络基础设施的升级以及应用程序和设备的适配等问题,目前全球网 络仍然广泛使用IPv4。然而,随着时间的推移,IPv6的推广和采用逐渐增加,以满足不断增长的互联网 连接需求。

17、什么是正向代理和反向代理?

正向代理(Forward Proxy)和反向代理(Reverse Proxy)是两种常见的代理服务器配置,用于在客户 端和目标服务器之间进行中间转发和处理。 正向代理:正向代理是位于客户端和目标服务器之间的代理服务器。当客户端发送请求时,请求首先被 发送到正向代理服务器,然后由代理服务器转发请求到目标服务器,并将响应返回给客户端。客户端通 常需要配置代理服务器的地址和端口,以便与目标服务器通信。 正向代理的主要功能包括:

  1. 隐藏客户端的真实IP地址:客户端的请求被代理服务器转发,目标服务器只能看到代理服务器的IP 地址,无法直接获取客户端的真实IP地址。
  2. 访问控制和过滤:代理服务器可以实施访问控制策略,限制客户端对目标服务器的访问。它还可以 过滤请求和响应,对流量进行审查和修改。
  3. 缓存:代理服务器可以缓存目标服务器的响应,以减轻目标服务器的负载并提高响应速度。
  4. 加速和优化:代理服务器可以对请求和响应进行优化,例如压缩、加密、负载均衡等,以提供更快 的访问速度和更好的性能。 反向代理:反向代理是位于目标服务器和客户端之间的代理服务器。当客户端发送请求时,请求首先被 发送到反向代理服务器,然后由代理服务器将请求转发到一个或多个目标服务器,最后将目标服务器的 响应返回给客户端。客户端不需要知道目标服务器的存在,只需要与反向代理服务器通信。 反向代理的主要功能包括:
  5. 负载均衡:反向代理可以将请求分发到多个目标服务器,以平衡服务器负载,提高系统的可扩展性 和容错性。
  6. 安全性和保护:反向代理可以充当防火墙,保护目标服务器免受恶意请求和攻击,例如DDoS攻 击。
  7. 缓存和加速:反向代理可以缓存目标服务器的响应,以提供更快的访问速度和更好的性能。
  8. SSL加密和解密:反向代理可以处理SSL/TLS加密和解密,减轻目标服务器的负载。 总结: 正向代理是代理客户端的请求,代表客户端与目标服务器通信;而反向代理是代理目标服务器的响应, 代表目标服务器与客户端通信。两者在网络架构中扮演不同的角色,提供不同的功能和优势。

18、什么是跨域访问问题,如何解决?

跨域访问问题(Cross-Origin Resource Sharing,CORS)是由浏览器的同源策略引起的。同源策略是 一种安全机制,它限制了一个网页中的脚本只能访问同源(相同协议、域名和端口)的资源,防止恶意 网站通过脚本访问其他网站的数据。 当网页中的 JavaScript 代码尝试从一个源(域)请求另一个源的资源时,浏览器会发出跨域请求,如果 目标资源的服务器没有正确配置跨域访问策略,浏览器会阻止该请求,从而导致跨域访问问题。 为了解决跨域访问问题,可以采取以下方法:

  1. 服务器端设置响应头:目标服务器可以在响应中设置特定的响应头来允许跨域访问。常见的响应头 是”Access-Control-Allow-Origin”,它指定允许访问资源的域。服务器可以设置该头为特定的域 名,或使用通配符”*“表示允许任意域名进行访问。例如,设置响应头:“Access-Control-Allow- Origin: *”
  2. 请求时添加额外的头信息:发起跨域请求时,可以在请求中添加一些额外的头信息,例 如”Origin”头,用于告知服务器请求的来源。服务器可以根据该头信息来判断是否允许跨域访问, 并设置相应的响应头。
  3. 使用代理:可以在自己的服务器上设置代理,将跨域请求转发到目标服务器。客户端与自己的服务 器进行通信,而自己的服务器与目标服务器进行通信,从而避免了浏览器的跨域限制。
  4. JSONP(JSON with Padding): JSONP是一种跨域访问的解决方案,它通过在页面中动态创建