2026版Java面试真题
- 2026-11-15 06:25:57
Spring 和 Spring Boot 有什么区别?
作为 Java 开发人员对 Spring 框架都很熟悉,Spring 为 Java 程序提供了全面的基础架构支持,包含了很多非常实用的功能,如 Spring JDBC、Spring AOP、Spring ORM、Spring Test 等,这些模块的出现,大大的缩短了应用程序的开发时间,同时提高了应用开发的效率。
Spring Boot 本质上是 Spring 框架的延伸和扩展,它的诞生是为了简化 Spring 框架初始搭建以及开发的过程,使用它可以不再依赖 Spring 应用程序中的 XML 配置,为更快、更高效的开发 Spring 提供更加有力的支持。
Spring Boot 有什么优点?
Spring Boot 具有以下特点:
开发变得简单:提供一键式项目创建和快速集成各种依赖功能; 配置变得简单:提供了丰富的 Starters,集成主流开源产品往往只需要简单的配置即可; 部署变得简单:其本身内嵌启动容器,仅仅需要一个命令即可启动项目,结合 Jenkins、Docker 自动化运维非常容易实现; 监控变得简单:提供了监控组件,使用 Actuator 轻松监控服务各项状态。
说一下 Spring Boot 启动流程?
Spring Boot 启动流程要从 main 方法的 run 源码说起,它的源码如下:
public ConfigurableApplicationContext run(String... args) {// 1.创建并启动计时监控类StopWatch stopWatch = new StopWatch();stopWatch.start();// 2.声明应用上下文对象和异常报告集合ConfigurableApplicationContext context = null;Collection exceptionReporters = new ArrayList();// 3.设置系统属性 headless 的值this.configureHeadlessProperty();// 4.创建所有 Spring 运行监听器并发布应用启动事件SpringApplicationRunListeners listeners = this.getRunListeners(args);listeners.starting();Collection exceptionReporters;try {// 5.处理 args 参数ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);// 6.准备环境ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);this.configureIgnoreBeanInfo(environment);// 7.创建 Banner 的打印类Banner printedBanner = this.printBanner(environment);// 8.创建应用上下文context = this.createApplicationContext();// 9.实例化异常报告器exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context);// 10.准备应用上下文this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);// 11.刷新应用上下文this.refreshContext(context);// 12.应用上下文刷新之后的事件的处理this.afterRefresh(context, applicationArguments);// 13.停止计时监控类stopWatch.stop();// 14.输出日志记录执行主类名、时间信息if (this.logStartupInfo) {(new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);}// 15.发布应用上下文启动完成事件listeners.started(context);// 16.执行所有 Runner 运行器this.callRunners(context, applicationArguments);} catch (Throwable var10) {this.handleRunFailure(context, var10, exceptionReporters, listeners);throw new IllegalStateException(var10);}try {// 17.发布应用上下文就绪事件listeners.running(context);// 18.返回应用上下文对象return context;} catch (Throwable var9) {this.handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners)null);throw new IllegalStateException(var9);}}
从以上源码可以看出 Spring Boot 的启动总共分为以下 18 个步骤。
1.创建并启动计时监控类
此计时器是为了监控并记录 Spring Boot 应用启动的时间的,它会记录当前任务的名称,然后开启计时器。
2.声明应用上下文对象和异常报告集合
此过程声明了应用上下文对象和一个异常报告的 ArrayList 集合。
3.设置系统属性 headless 的值
设置 Java.awt.headless = true,其中awt(Abstract Window Toolkit)的含义是抽象窗口工具集。设置为 true表示运行一个 headless 服务器,可以用它来作一些简单的图像处理。
4.创建所有 Spring 运行监听器并发布应用启动事件
此过程用于获取配置的监听器名称并实例化所有的类。
5.初始化默认应用的参数类
也就是说声明并创建一个应用参数对象。
6.准备环境
创建配置并且绑定环境(通过 property sources 和 profiles 等配置文件)。
7.创建 Banner 的打印类
Spring Boot 启动时会打印 Banner 图片
此 banner 信息是在 SpringBootBanner 类中定义的,我们可以通过实现 Banner 接口来自定义 banner 信息,然后通过代码 setBanner() 方法设置 Spring Boot 项目使用自己自定义 Banner 信息,或者是在 resources 下添加一个 banner.txt,把 banner 信息添加到此文件中,就可以实现自定义 banner 的功能了。
8.创建应用上下文
根据不同的应用类型来创建不同的 ApplicationContext上下文对象。
9.实例化异常报告器
它调用的是 getSpringFactoriesInstances() 方法来获取配置异常类的名称,并实例化所有的异常处理类。
10.准备应用上下文
此方法的主要作用是把上面已经创建好的对象,传递给 prepareContext 来准备上下文,例如将环境变量 environment 对象绑定到上下文中、配置 bean 生成器以及资源加载器、记录启动日志等操作。
11.刷新应用上下文
此方法用于解析配置文件,加载 bean 对象,并且启动内置的 web 容器等操作。
12.应用上下文刷新之后的事件处理
这个方法的源码是空的,可以做一些自定义的后置处理操作。
13.停止计时监控类
停止此过程第一步中的程序计时器,并统计任务的执行信息。
14.输出日志信息
把相关的记录信息,如类名、时间等信息进行控制台输出。
15.发布应用上下文启动完成事件
触发所有 SpringApplicationRunListener 监听器的 started 事件方法。
16.执行所有 Runner 运行器
执行所有的 ApplicationRunner 和 CommandLineRunner 运行器。
17.发布应用上下文就绪事件
触发所有的 SpringApplicationRunListener 监听器的 running 事件。
18.返回应用上下文对象
到此为止 SpringBoot 的启动程序就结束了,我们就可以正常来使用 Spring Boot 框架了。
Ant、Maven 和 Gradle 有什么区别?
Ant、Maven、Gradle 是 Java 领域中主要有三大构建工具,它们的区别如下:
Ant(AnotherNeatTool)诞生于 2000 年,是由 Java 编写,采用 XML 作为构建脚本,这样就允许你在任何环境下运行构建。Ant 是 Java 领域最早的构建工具,不过因为操作复杂,慢慢的已经被淘汰了; Maven 诞生于 2004 年,目的是解决程序员使用 Ant 所带来的一些问题,它的好处在于可以将项目过程规范化、自动化、高效化以及强大的可扩展性; Gradle 诞生于 2009 年,是一个基于 Apache Ant 和 Apache Maven 概念的项目自动化建构工具。它使用一种基于 Groovy 的特定领域语言来声明项目设置,而不是传统的 XML。结合了前两者的优点,在此基础之上做了很多改进,它具有 Ant 的强大和灵活,又有 Maven 的生命周期管理且易于使用。
Spring Boot 官方支持 Maven 和 Gradle 作为项目构建工具。Gradle 虽然有更好的理念,但是相比 Maven 来讲其行业使用率偏低。
properties 和 yml 有什么区别?
properties 和 yml 都是 Spring Boot 支持的两种配置文件,其中 yml 格式的配置文件可以看作是对 properties 配置文件的升级。它们的主要区别有 4 点:
定义和定位不同:properties 翻译成中文是“属性”的意思,properties 只是用来设置一些属性配置的;yml 是 YAML 是缩写,它的全称 Yet Another Markup Language,翻译成中文是“另一种标记语言”。它的定位是“另一种标记语言”,所以从格局上来讲 yml 就甩 properties 好几条街; 语法不同:yml 的语法更简单,且可读性更高; 支持数据类型不同:yml 可以更好的配置多种数据类型,比如对象、集合等; 跨语言不同:yml 可以跨语言使用,通用性更好。
SpringBoot 如何读取配置文件?
在 Spring Boot 中读取配置文件有以下 5 种方法:
使用 @Value 读取配置文件。 使用 @ConfigurationProperties 读取配置文件。 使用 @PropertySource 读取配置文件。 使用 Environment 读取配置文件。 使用原生方式读取配置文件。
其中最常用的是前 3 种,如果读取某一个配置项可使用 @Value,如果读取一组配置项可使用 @ConfigurationProperties,如果要指定读取某一个具体的配置文件可使用 @PropertySource 来指定。
配置文件中中文乱码问题如何处理?
配置文件中的中文乱码问题的解决方案如下:
将配置文件的编码格式设置为支持中文的 UTF-8 格式; 使用 @PropertySource 设置支持中文的 UTF-8 格式读取,如 @PropertySource(value = "dev.properties", encoding = "utf-8")。
如何实现拦截器?
在 Spring Boot 中拦截器的实现分为两步:
创建一个普通的拦截器,实现 HandlerInterceptor 接口,并重写接口中的相关方法; 将上一步创建的拦截器加入到 Spring Boot 的配置文件中,并配置拦截规则。
具体实现如下。
① 实现自定义拦截器
import org.springframework.stereotype.Component;import org.springframework.web.servlet.HandlerInterceptor;import org.springframework.web.servlet.ModelAndView;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;@Componentpublic class TestInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {System.out.println(”拦截器:执行 preHandle 方法。”);return true;}@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {System.out.println(”拦截器:执行 postHandle 方法。”);}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {System.out.println(”拦截器:执行 afterCompletion 方法。”);}}
其中:
boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handle):在请求方法执行前被调用,也就是调用目标方法之前被调用。比如我们在操作数据之前先要验证用户的登录信息,就可以在此方法中实现,如果验证成功则返回 true,继续执行数据操作业务;否则就返回 false,后续操作数据的业务就不会被执行了。 void postHandle(HttpServletRequest request, HttpServletResponse response, Object handle, ModelAndView modelAndView):调用请求方法之后执行,但它会在 DispatcherServlet 进行渲染视图之前被执行。 void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handle, Exception ex):会在整个请求结束之后再执行,也就是在 DispatcherServlet 渲染了对应的视图之后再执行。
② 配置拦截规则
最后,我们再将上面的拦截器注入到项目配置文件中,并设置相应拦截规则,具体实现代码如下:
import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.annotation.Configuration;import org.springframework.web.servlet.config.annotation.InterceptorRegistry;import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;@Configurationpublic class AppConfig implements WebMvcConfigurer {// 注入拦截器@Autowiredprivate TestInterceptor testInterceptor;@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(testInterceptor) // 添加拦截器.addPathPatterns(”/**”); // 拦截所有地址.excludePathPatterns(”/login”); // 放行接口}}
如何实现过滤器?
过滤器可以使用 Servlet 3.0 提供的 @WebFilter 注解,配置过滤的 URL 规则,然后再实现 Filter 接口,重写接口中的 doFilter 方法,具体实现代码如下:
import org.springframework.stereotype.Component;import javax.servlet.*;import javax.servlet.annotation.WebFilter;import java.io.IOException;@Component@WebFilter(urlPatterns = ”/*”)public class TestFilter implements Filter {@Overridepublic void init(FilterConfig filterConfig) throws ServletException {System.out.println(”过滤器:执行 init 方法。”);}@Overridepublic void doFilter(ServletRequest servletRequest,ServletResponse servletResponse,FilterChain filterChain) throws IOException, ServletException {System.out.println(”过滤器:开始执行 doFilter 方法。”);// 请求放行filterChain.doFilter(servletRequest, servletResponse);System.out.println(”过滤器:结束执行 doFilter 方法。”);}@Overridepublic void destroy() {System.out.println(”过滤器:执行 destroy 方法。”);}}
其中:
void init(FilterConfig filterConfig):容器启动(初始化 Filter)时会被调用,整个程序运行期只会被调用一次。用于实现 Filter 对象的初始化。 void doFilter(ServletRequest request, ServletResponse response,FilterChain chain):具体的过滤功能实现代码,通过此方法对请求进行过滤处理,其中 FilterChain 参数是用来调用下一个过滤器或执行下一个流程。 void destroy():用于 Filter 销毁前完成相关资源的回收工作。
拦截器和过滤器有什么区别?
拦截器和过滤器的区别主要体现在以下 5 点:
出身不同:过滤器来自于 Servlet,而拦截器来自于 Spring 框架; 触发时机不同:请求的执行顺序是:请求进入容器 > 进入过滤器 > 进入 Servlet > 进入拦截器 > 执行控制器(Controller),所以过滤器和拦截器的执行时机,是过滤器会先执行,然后才会执行拦截器,最后才会进入真正的要调用的方法; 底层实现不同:过滤器是基于方法回调实现的,拦截器是基于动态代理(底层是反射)实现的。; 支持的项目类型不同:过滤器是 Servlet 规范中定义的,所以过滤器要依赖 Servlet 容器,它只能用在 Web 项目中;而拦截器是 Spring 中的一个组件,因此拦截器既可以用在 Web 项目中,同时还可以用在 Application 或 Swing 程序中; 使用的场景不同:因为拦截器更接近业务系统,所以拦截器主要用来实现项目中的业务判断的,比如:登录判断、权限判断、日志记录等业务;而过滤器通常是用来实现通用功能过滤的,比如:敏感词过滤、字符集编码设置、响应数据压缩等功能。
如何操作事务?
在 Spring Boot 中操作事务有两种方式:编程式事务或声明式事务。
① 编程式事务有两种实现方法
使用 TransactionTemplate 对象实现编程式事务; 使用更加底层的 TransactionManager 对象实现编程式事务。
a) TransactionTemplate 实现
要使用 TransactionTemplate 对象需要先将 TransactionTemplate 注入到当前类中 ,然后再使用它提供的 execute 方法执行事务并返回相应的执行结果,如果程序在执行途中出现了异常,那么就可以使用代码手动回滚事务。
b) TransactionManager 实现
TransactionManager 实现编程式事务相对麻烦一点,它需要使用两个对象:TransactionManager 的子类,加上 TransactionDefinition 事务定义对象,再通过调用 TransactionManager 的 getTransaction 获取并开启事务,然后调用 TransactionManager 提供的 commit 方法提交事务,或使用它的另一个方法 rollback 回滚事务。
② 声明式事务
声明式事务的实现比较简单,只需要在方法上或类上添加 @Transactional 注解即可,当加入了 @Transactional 注解就可以实现在方法执行前,自动开启事务;在方法成功执行完,自动提交事务;如果方法在执行期间,出现了异常,那么它会自动回滚事务。
导致 @Transactional 失效的场景有哪些?
导致 @Transactional 失效的常见场景有以下 5 个:
非 public 修饰的方法; timeout 超时时间设置过小:如果事务的超时时间设置过小,而方法执行时间过长(超过 timeout),那么事务也会失效; 代码中使用 try/catch 处理异常; 调用类内部的 @Transactional 方法; 数据库不支持事务。
为什么非 public 方法 @Transactional 会失效?
因为 @Transactional 使用的是 Spring AOP 实现的,而 Spring AOP 是通过动态代理实现的,而 @Transactional 在生成代理时会判断,如果方法为非 public 修饰的方法,则不生成代理对象,这样也就没办法自动执行事务了,它的部分实现源码如下:
protected TransactionAttribute computeTransactionAttribute(Method method, Class<?> targetClass) {// 非 public 方法,设置为 nullif (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {return null;}// 后面代码省略....}
为什么 try-catch 之后,事务不能自动回滚了?
造成这个问题的原因是和 @Transactional 注解的实现有关的,因为 @Transactional 在实现中,会捕捉异常,如果有异常了才会回滚事务,而程序中如果添加了 try-catch 之后,@Transactional 就感知不到异常了,从而也就不会回滚事务了,@Transactional 的部分实现源码如下:
protected Object invokeWithinTransaction(Method method, Class<?> targetClass, final InvocationCallback invocation)throws Throwable {final TransactionAttribute txAttr = getTransactionAttributeSource().getTransactionAttribute(method, targetClass);final PlatformTransactionManager tm = determineTransactionManager(txAttr);final String joinpointIdentification = methodIdentification(method, targetClass);if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {// 自动开启事务TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);Object retVal = null;try {// 反射调用业务方法retVal = invocation.proceedWithInvocation();}catch (Throwable ex) {// 异常时,在 catch 逻辑中,自动回滚事务completeTransactionAfterThrowing(txInfo, ex);throw ex;}finally {cleanupTransactionInfo(txInfo);}// 自动提交事务commitTransactionAfterReturning(txInfo);return retVal;}else {// .....}}
如何处理事务不自动回滚的问题?
当 @Transactional 感知不到异常时,也就是程序中加入了 try-catch 之后事务就不自动回滚了,此时的解决方案有两种:
在 catch 中重新将异常抛出(这样 @Transactional 就能感知到异常了); 使用代码手动回滚事务。
具体实现如下。
① 重新抛出异常
@RequestMapping(”/save”)@Transactional(isolation = Isolation.SERIALIZABLE)public Object save(User user) {// 业务代码...try {// 异常代码int i = 10 / 0;} catch (Exception e) {// 将异常重新抛出去throw e;}return result;}
② 手动回滚事务
在方法中使用 TransactionAspectSupport.currentTransactionStatus() 可以得到当前的事务,然后设置回滚方法 setRollbackOnly 就可以实现回滚了,具体实现代码如下:
@RequestMapping(”/save”)@Transactionalpublic Object save(User user) {// 插入数据库int result = userService.save(user);try {// 异常代码int i = 10 / 0;} catch (Exception e) {// 手动回滚事务 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();}return result;}
为什么调用类内部 @Transactional 事务会失效?
因为 @Transactional 是基于 Spring AOP 实现的,而 Spring AOP 又是基于动态代理实现的,而当调用类内部的方法时,不是通过代理对象完成的,而是通过 this 对象实现的,这样就绕过了代理对象,从而事务就失效了。
说一下 @Transactional 工作原理?
@Transactional 是基于 AOP 实现的,AOP 又是使用动态代理实现的。如果目标对象实现了接口,默认情况下会采用 JDK 的动态代理,如果目标对象没有实现了接口,会使用 CGLIB 动态代理。@Transactional 在开始执行业务之前,通过代理先开启事务,在执行成功之后再提交事务。如果中途遇到的异常,则回滚事务。
Spring 有几种事务隔离级别?
Spring 中事务隔离级别包含以下 5 种:
DEFAULT:Spring 默认的事务隔离级别,以连接的数据库的事务隔离级别为准; READ_UNCOMMITTED:读未提交,也叫未提交读,该隔离级别的事务可以看到其他事务中未提交的数据。该隔离级别因为可以读取到其他事务中未提交的数据,而未提交的数据可能会发生回滚,因此我们把该级别读取到的数据称之为脏数据,把这个问题称之为脏读; READ_COMMITTED:读已提交,也叫提交读,该隔离级别的事务能读取到已经提交事务的数据,因此它不会有脏读问题。但由于在事务的执行中可以读取到其他事务提交的结果,所以在不同时间的相同 SQL 查询中,可能会得到不同的结果,这种现象叫做不可重复读; REPEATABLE_READ:可重复读,它能确保同一事务多次查询的结果一致。但也会有新的问题,比如此级别的事务正在执行时,另一个事务成功的插入了某条数据,但因为它每次查询的结果都是一样的,所以会导致查询不到这条数据,自己重复插入时又失败(因为唯一约束的原因)。明明在事务中查询不到这条信息,但自己就是插入不进去,这就叫幻读 (Phantom Read); SERIALIZABLE:串行化,最高的事务隔离级别,它会强制事务排序,使之不会发生冲突,从而解决了脏读、不可重复读和幻读问题,但因为执行效率低,所以真正使用的场景并不多。
说一下 Spring 事务传播机制?
Spring 事务传播机制是指,包含多个事务的方法在相互调用时,事务是如何在这些方法间传播的。
Spring 事务传播机制可使用 @Transactional(propagation=Propagation.REQUIRED) 来定义,Spring 事务传播机制的级别包含以下 7 种:
Propagation.REQUIRED:默认的事务传播级别,它表示如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。 Propagation.SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。 Propagation.MANDATORY:(mandatory:强制性)如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。 Propagation.REQUIRES_NEW:表示创建一个新的事务,如果当前存在事务,则把当前事务挂起。也就是说不管外部方法是否开启事务,Propagation.REQUIRES_NEW 修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不干扰。 Propagation.NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。 Propagation.NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。 Propagation.NESTED:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于 PROPAGATION_REQUIRED。
加入事务和嵌套事务有什么区别?
加入事务(REQUIRED)和嵌套事务(NESTED)都是事务传播机制中的两种传播级别,如果当前不存在事务,那么二者的行为是一致的;但如果当前存在事务,那么加入事务的事务传播级别当遇到异常时会回滚全部事务,而嵌套事务则是回滚部分事务。嵌套事务之所以能回滚部分事务,是因为数据库中存在一个保存点的概念,嵌套事务相对于新建了一个保存点,如果出现异常了,那么只需要回滚到保存点即可,这样就实现了部分事务的回滚。
事务隔离级别和传播机制有什么区别?
事务隔离级别描述的是多个事务同时执行时的某种行为;而事务传播机制是描述,包含了多个事务的方法在相互调用时事务的传播行为。所以事务隔离级别描述的是纵向事务并发调用时的行为模式,而事务传播机制描述的是横向事务传递时的行为模式
如何进行统一异常处理?
在 Spring Boot 中,统一异常处理可以使用 @ControllerAdvice + @ExceptionHandler 来实现,@ControllerAdvice 表示控制器通知类,@ExceptionHandler 是异常处理器,两个结合表示当出现异常的时候执行某个通知,也就是执行某个方法事件,具体实现代码如下:
import java.util.HashMap;@ControllerAdvicepublic class ErrorAdive {@ExceptionHandler(Exception.class)@ResponseBodypublic Object handler(Exception e) {HashMap map = new HashMap<>();map.put(”code”, 0);map.put(”data”, null);map.put(”msg”, e.getMessage());return map;}}
这样写完之后,当项目中有异常时就会触发此方法,返回固定格式的异常信息。
Java 有几种架构类型?
对于 Java 语言来说,它的发展经历了单体架构 -> SOA 面向服务架构 -> 微服务架构。
① 单体架构
在程序规模不大,开发人员很少的时候,下面的有点是非常显著的:
开发简单:单体应用的结构,天然决定了所有代码都集中在一起,开发者不需要在多个应用之间来回跳转来寻找其中的调用逻辑。 测试简单:所有代码都在一个应用里,测试人员可以很方便的做到端到端的测试(当然,很多时候测试人员就是开发者自己)。 部署简单:因为一个应用就是产品功能的全集,所以在部署的时候,只需要不是一款应用即可。即使是集群部署,也不会增加多少复杂度:只需要将应用部署多份即可。 开发迅速。上面的各种简单,带来的就是软件功能可以快速实现。很多时候,实现需求的速度是项目成功与否的决定性因素。
所以,在开发简单&独立的产品时,单体架构依然是第一优先选择。
但是单体应用随着功能的持续增加、团队规模的不断扩大,我们很快就会发现单体应用的弊端:
应用膨胀:所有代码都在一个应用里,导致应用的代码量迅速上升,对于开发者来说,经常需要在海量的代码里找到自己需要维护的哪一行,这种体验往往是令人崩溃的。同时,对于 IDE 来说,一个应用内大量代码也会严重拖慢其运行效率。 团队合作冲突:这种冲突会体现在多个方面,在开发阶段,很容易由于修改相同的代码导致代码冲突。部署阶段,又会因为“运行环境里跑的是谁的分支”而造成新的冲突。所有的这些冲突将会严重影响到团队的合作效率。 运行效率&稳定性:单体应用,由于逻辑都集中在一起,启动时需要完成所有的初始化工作;同时单一功能的问题也会因为运行在一个进程内,从而导致整个应用宕机。
② SOA 框架
SOA 是 Service-Oriented Architecture 的简写,直译为“面向服务的架构”,从命名上就可以看出“服务”是 SOA 架构里是非常重要的概念。SOA 的核心思想是“将系统的功能解构为一系列服务”,也就是将之前单体架构分而治之,拆分开来。
SOA 会按照业务领域对应用进行粗粒度的垂直拆分,这样一个项目就会变成一些相对独立的应用,但此时,应用间的依赖、调用等相关问题就浮现出来了。
SOA 框架的缺点如下:
首先就是对 SOA 的解释缺乏统一标准,上文的引用的定义也只是众多解释中使用的较为通用的一种。甚至可以这么说:一千个人眼中,有一千种 SOA 。基于此,很多厂商便借用 SOA 的大旗来推广自己的产品和标准,这又进一步加剧了问题的严重性; 高门槛:SOA 对开发人员的要求很高; 厂商绑定:由于缺乏统一保准,不同厂商的解决方案之间很难做切换; 不适应云环境:在如今的互联网时代,速度就是一切。由此诞生了敏捷开发、持续集成等在不同节点提升业务上线速度的办法。但是方向是不一致的; 中心化:虽然应用本身实现了分布式与水平扩展,但是 SOA 需要使用企业服务总线 ESB(Enterprise Service Bus)来实现应用的依赖和调用,但同时 ESB 也成了系统的中枢神经。
③ 微服务架构
微服务是将一个应用拆分成多个松耦合的服务,这些服务之间通过某种协议(REST、RPC等)进行互相协作,完成原单体架构下的业务功能,但提供更灵活的部署模式,更容易扩展,降低了开发、运维上的复杂度。对于微服务来说,其中一个关键点就是各服务之间的松耦合,各服务之间通过一种“标准”的协议进行沟通,不需要理解对方服务的实现逻辑、实现方式,只要在对方所提供的服务接口没有变化的情况下不会影响自己所提供的服务功能即可。总而言之,微服务核心思路就是分而治之。
RESTful 和 RPC 有什么区别?
RPC 和 RESTful 都是远程调用接口。
RESTful 是一种网络应用程序的设计风格和开发方式,基于 HTTP 协议,可以使用 XML 格式定义或 JSON 格式定义。RESTful 适用于移动互联网厂商作为业务接口的场景,实现第三方调用移动网络资源的功能,动作类型为新增、变更、删除所调用资源。
RPC(Remote Procedure Call,远程过程调用)是指计算机程序使过程在不同的地址空间(通常在共享网络的另一台计算机上)执行时,其编码方式就像是正常的(本地)过程调用,而无需程序员明确为远程交互编码细节。RPC是一种服务器-客户端(Client/Server)模式,经典实现是一个通过发送请求-接受回应进行信息交互的系统。RPC出现的最初目的,就是为了让计算机能够与调用本地方法一样去调用远程方法。
总体来说:RPC 是以一种调用本地方法的思路来调用远程方法,通过各种 RPC 框架隐藏调用远程方法的细节,让用户以为调用的就是本地方法。RPC 隐藏了底层网络通信的复杂度,让我们更专注于业务逻辑的开发。
RESTful 通过 HTTP 实现,把用户的需求抽象成对资源的操作,用户必须通过 HTTP 协议的 GET、HEAD、POST、PUT、DELETE、TRACE、OPTIONS 七种基本操作去和服务器交互。
RPC 通常是服务器和服务器之间的通信,比如和中间件的通信,MQ、分布式缓存、分布式数据库等等。
而 REST 通常是面向客户端的(一般是浏览器),他们的使用场景也是不一样的。
微服务中的“服务”怎么理解?
微服务中的服务是一个可以独立运行、提供范围有限的功能(可以是业务功能,也有可能是非业务功能)的组件。功能具体实现隐藏在组件内部,而对外则提供访问接口,外部其他服务可以通过这些接口进行访问与交互,从这一方面来说,微服务是可以单独部署运行的。
微服务有什么优点?
微服务的设计理念参考了 UNIX 系统,每个微服务仅承担一种职责,并把该职责做到最好。因此,这让我们的应用看起来是一系列服务的集合。微服务架构具有以下优点。
可扩展性更强:可以根据每个微服务的性能要求和业务特点来对服务进行灵活扩展,比如通过增加单个服务的集群规模,提升部署了该服务的节点的硬件配置。 技术选型更灵活:每个微服务都由不同的团队来维护,所以可以结合业务特性自由选择技术栈。 复杂度可控:通过对共享业务服务更细粒度的拆分,一个服务只需要关注一个特定的业务领域,并通过定义良好的接口清晰表述服务边界。由于体积小、复杂度低,开发、维护会更加简单。 独立部署:由于每个微服务都是一个独立运行的进程,所以可以实现独立部署。当某个微服务发生变更时不需要重新编译部署整个应用,并且单个微服务的代码量比较小,使得发布更加高效。 容错性:在微服务架构中,如果某一个服务发生故障,我们可以使故障隔离在单个服务中。其他服务可以通过重试、降级等机制来实现应用层面的容错。
微服务有什么缺点?
微服务的缺点有以下几个:
故障排查:一次请求可能会经历多个不同的微服务的多次交互,交互的链路可能会比较长,每个微服务会产生自己的日志,在这种情况下如果出现一个故障,开发人员定位问题的根源会比较困难。 分布式架构的复杂性:微服务本身构建的是一个分布式系统,分布式系统涉及服务之间的远程通信,而网络通信中网络的延迟和网络故障是无法避免的,从而增加了应用程序的复杂度。 服务监控:在一个单体架构中很容易实现服务的监控,因为所有的功能都在一个服务中。在微服务架构中,服务监控开销会非常大,可以想象一下,在几百个微服务组成的架构中,我们不仅要对整个链路进行监控,还需要对每一个微服务都实现一套类似单体架构的监控。 运维成本:在微服务中,需要保证几百个微服务的正常运行,对于运维的挑战是巨大的。比如单个服务流量激增时如何快速扩容、服务拆分之后导致故障点增多如何处理、如何快速部署和统一管理众多的服务等。 服务依赖:微服务数量增加之后,各个服务之间会存在更多的依赖关系,使得系统整体更为复杂。假设你在完成一个案例,需要修改服务A、B、C,而A依赖B,B依赖C。在单体式应用中,你只需要改变相关模块,整合变化,再部署就好了。对比之下,微服务架构模式就需要考虑相关改变对不同服务的影响。比如,你需要更新服务C,然后是B,最后才是A,幸运的是,许多改变一般只影响一个服务,需要协调多服务的改变很少。 学习成本更大:微服务架构虽然可以将业务分解为更小、更容易开发的模式,但是也需要开发人员学习掌握一系列的微服务开发技术,加大了进入门槛,这也是非常有难度的一个挑战。
微服务中有哪些组件?
微服务中的重要组件有以下几个。
服务注册与发现 配置中心 网关 负载均衡 熔断限流 链路监控
微服务和 Spring Cloud 有什么关系?
很多人认为微服务就是 Spring Cloud,Spring Cloud 就等于微服务,但严格意义上来说,微服务和 Spring Cloud 之间是不能直接划上等号的。微服务是一种架构和项目实现思想,而 Spring Cloud 是一套技术解决方案。比如在 Spring Cloud 出现之前,各个微服务之间的调用是非常痛苦的事,虽然市面上有很多开源的 RPC(Remote Procedure Call,远程调用)框架,如 Thrift、Dubbo、Motan、gRPC 等,但如何做好服务的动态感知就是一个非常复杂的事,比如 A 服务要调用 B 服务,但是 B 服务部署了 10 个节点,那么 A 服务如何动态维护 B 服务的 10 个不同的地址信息呢?直到 Spring Cloud 的出现,便解决了这些难题。所以可以认为 Spring Cloud 为微服务提供了一种优秀的解决方案。
什么是 Spring Cloud?
Spring Cloud 是 Pivotal 公司在 2015 年发布的一个项目,当然,Spring 和 Spring Boot 也是 Pivotal 公司发布的产品。Spring Cloud 提供了一些可以让开发者快速构建微服务应用的工具,比如配置管理、服务发现、熔断、智能路由等,这些服务可以在任何分布式环境下很好地工作。Spring Cloud 主要致力于解决如下问题:
Distributed/versioned configuration:分布式及版化配置; Service registration and discovery:服务注册与发现; Routing:服务路由; Service-to-service calls:服务调用; Load balancing:负载均衡; Circuit Breakers:断路器; Global locks:全局锁; Leadership election and cluster state:Leader 选举及集群状态; Distributed messaging:分布式消息。
需要注意的是,Spring Cloud 并不是 Spring 团队全新研发的框架,它只是把一些比较优秀的解决微服务架构中常见问题的开源框架基于 Spring Cloud 规范进行了整合,通过 Spring Boot 这个框架进行再次封装后屏蔽掉了复杂的配置,给开发者提供良好的开箱即用的微服务开发体验。所以,Spring Cloud 其实就是一套规范,而 Spring Cloud Netflix、Spring Cloud Alibaba 才是 Spring Cloud 规范的实现。
Spring Boot 和 Spring Cloud 有什么区别?
它们的区别有以下几个:
Spring Boot 专注于快速方便的开发单个个体微服务。 Spring Cloud 是关注全局的微服务协调整理治理框架,它将 Spring Boot 开发的一个个单体微服务整合并管理起来,为各个微服务之间提供,配置管理、服务发现、断路器、路由、微代理、事件总线、全局锁、决策竞选、分布式会话等等集成服务。 Spring Boot 可以离开 Spring Cloud 独立使用开发项目, 但是 Spring Cloud 离不开 Spring Boot。Spring Cloud 项目都是基于 Spring Boot 创建的,所以 Spring Cloud 需要依赖 Spring Boot。 Spring Boot 专注于快速、方便的开发单个微服务个体,Spring Cloud 关注全局的服务治理框架。
Spring Cloud 和 Dubbo 有什么区别?
Spring Cloud 提供了一些可以让开发者快速构建微服务应用的工具,比如配置管理、服务发现、熔断、智能路由等,这些服务可以在任何分布式环境下很好地工作。而 Dubbo 是一款微服务框架,为大规模微服务实践提供高性能 RPC 通信、流量治理、可观测性等解决方案,涵盖 Java、Golang 等多种语言 SDK 实现。
它们最大区别的是 Spring Cloud 抛弃了 Dubbo 的 RPC 通信,采用的是基于 HTTP 的 RESTFul 方式请求。但 Dubbo 的通讯性能更高,而 Spring Cloud 提供的功能更全。
什么是 Spring Cloud Netflix?
Spring Cloud Netflix 是 Netflix(美国奈飞公司)提供的 Spring Cloud 规范的具体实现框架,它提供了以下组件:
Eureka:服务注册与发现,它提供了一个服务注册中心、服务发现的客户端,还有一个方便的查看所有注册的服务的界面。 所有的服务使用 Eureka 的服务发现客户端来将自己注册到 Eureka 的服务器上; Zuul:服务网关,所有的客户端请求通过这个网关访问后台的服务。他可以使用一定的路由配置来判断某一个 URL 由哪个服务来处理。并从 Eureka 获取注册的服务来转发请求; Ribbon:负载均衡,Zuul 网关将一个请求发送给某一个服务的应用的时候,如果一个服务启动了多个实例,就会通过 Ribbon 来通过一定的负载均衡策略来发送给某一个服务实例; Feign:远程服务的客户端代理,服务之间如果需要相互访问,可以使用 RestTemplate,也可以使用Feign客户端访问。它默认会使用 Ribbon 来实现负载均衡; Hystrix:断路器,提供服务熔断和限流功能,我们只需要在服务接口上添加 Hystrix 标签,就可以实现对这个接口的监控和断路器功能; Hystrix Dashboard:监控面板,监控和断路器。我们只需要在服务接口上添加Hystrix标签,就可以实现对这个接口的监控和断路器功能; Turbine:将各个服务实例上的 Hystrix 监控信息进行统一聚合,使用 Hystrix 监控,我们需要打开每一个服务实例的监控信息来查看。而 Turbine 可以帮助我们把所有的服务实例的监控信息聚合到一个地方统一查看。
Netflix 本身是一套非常好的组件,由于 Netflix 对 Zuul 1、Ribbon、Archaius 等组件的维护不利,Spring Cloud 决定在 Greenwich 中将如下项目都改为“维护模式”(进入维护模式意味着这些组件以后不会有大的功能更新,只会修复 Block 级别的 Bug 及安全问题)。当然,这些组件短期来说仍然可以继续使用,但是长期来看显然是不合适的。
不过我们也不用太担心这个问题,因为 Spring 官方提供了替换方案,如新发布的 Spring Cloud Alibaba 就是一个不错的替代 Spring Cloud Netflix 的组件。
Eureka 和 ZooKeeper 有什么区别?
从功能上来说 Eureka 和 ZooKeeper都可以实现服务注册与发现,但它们主要有以下四点区别。
① ZooKeeper 保证的是 CP 而 Eureka 保证的是 AP
CAP 含义如下:
C:Consisteny(一致性),比如数据库是主从模式,一个写库请求进来了,主库完成了写入操作,但是再(从库)同步数据之前,另一个用户查了这条数据,结果没查到,但是也没报错,这就不是强一致性。虽然最终会同步成功,但这是最终一致性的体现。强一致性的体现在于我不管你因为什么没同步成功(可能网络延迟或其他等),只要没同步成功,我这个从库就不能对外提供服务。必须主从数据一致才可以提供服务。 A:Availability(可用性),还是上面的例子,就是保证了可用性。因为虽然主从没同步完成,但是我从库照样能提供服务而且及时响应结果。也就是说可用性保证服务可用,而不在乎数据是否一致。 P:Partition Tolerance(分区容错性),集群部署了三台服务,挂了一台,其他两台还能继续对外提供服务,这时候我就认为他是没问题的,也就是我能容忍你挂了一台,只要还有服务能对外提供请求即可。所以一般分区容忍性是必须的,一般都需要从 C 和 A 之间做选择。
所以 CP 模式既一致性和分区容忍性,而 AP 模式即可用性和分区容忍性。
ZooKeeper 在选举期间注册服务瘫痪,虽然服务最终会恢复,但是选举期间不可用的;Eureka 各个节点是平等关系,只要有一台 Eureka 就可以保证服务可用,而查询到的数据并不是最新的。
② ZooKeeper 有 Leader 和 Follower 角色之分,而 Eureka 各个节点平等。
③ ZooKeeper 采用过半数存活原则,Eureka 采用自我保护机制解决分区问题。
什么是 Eureka 的自我保护机制?
当Eureka Server 节点在短时间内丢失了过多实例的连接时,比如网络故障或频繁启动关闭客户端时,节点会进入自我保护模式,保护注册信息,不再删除注册数据。故障恢复时,才会自动退出自我保护模式。
Spring Cloud Alibaba 提供了什么功能?
Spring Cloud Alibaba 2018 年正式入驻 Spring Cloud 官方孵化器,并发布了第一个预览版本。2019 年 8 月在 Alibaba 仓库发布第一个毕业版本。它提供的主要功能有以下几个:
服务限流降级:默认支持 WebServlet、WebFlux、OpenFeign、RestTemplate、Spring Cloud Gateway、Dubbo 和 RocketMQ 限流降级功能的接入,可以在运行时通过控制台实时修改限流降级规则,还支持查看限流降级 Metrics 监控。 服务注册与发现:适配 Spring Cloud 服务注册与发现标准,默认集成了 Ribbon 的支持。 分布式配置管理:支持分布式系统中的外部化配置,配置更改时自动刷新。 消息驱动能力:基于 Spring Cloud Stream 为微服务应用构建消息驱动能力。 分布式事务:使用 @GlobalTransactional 注解, 高效并且对业务零侵入地解决分布式事务问题。 阿里云对象存储:阿里云提供的海量、安全、低成本、高可靠的云存储服务。支持在任何应用、任何时间、任何地点存储和访问任意类型的数据。 分布式任务调度:提供秒级、精准、高可靠、高可用的定时(基于 Cron 表达式)任务调度服务。同时提供分布式的任务执行模型,如网格任务。网格任务支持海量子任务均匀分配到所有 Worker(schedulerx-client)上执行。 阿里云短信服务:覆盖全球的短信服务,友好、高效、智能的互联化通讯能力,帮助企业迅速搭建客户触达通道。
Spring Cloud Alibaba 有哪些组件?
它提供的重要组件有以下几个:
Sentinel:把流量作为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。 Nacos:一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。 RocketMQ:一款开源的分布式消息系统,基于高可用分布式集群技术,提供低延时的、高可靠的消息发布与订阅服务。 Seata:一个易于使用的高性能微服务分布式事务解决方案。 Alibaba Cloud OSS:阿里云对象存储服务(Object Storage Service,简称 OSS),是阿里云提供的海量、安全、低成本、高可靠的云存储服务。您可以在任何应用、任何时间、任何地点存储和访问任意类型的数据。 Dubbo:提供高性能 RPC 通信、流量治理、可观测性等解决方案,它涵盖 Java、Golang 等多种语言 SDK 实现。 Alibaba Cloud SchedulerX:阿里中间件团队开发的一款分布式任务调度产品,提供秒级、精准、高可靠、高可用的定时(基于 Cron 表达式)任务调度服务。 Alibaba Cloud SMS:覆盖全球的短信服务,友好、高效、智能的互联化通讯能力,帮助企业迅速搭建客户触达通道。
什么是服务注册和服务发现?
服务注册和发现的意思是服务进程在注册中心注册自己的位置,客户端应用进程向注册中心发起查询,来获取服务的位置,服务发现的一个重要作用就是提供一个可用的服务列表。
服务注册:服务进程在注册中心注册自己的位置。它通常注册自己的主机和端口号,有时还有身份验证信息,协议,版本号,以及运行环境的详细资料。 服务发现:客户端应用进程向注册中心发起查询,来获取服务的位置。服务发现的一个重要作用就是提供一个可用的服务列表。
服务发现有几种模式?
服务发现主要有两种发现模式:客户端发现和服务端发现。
客户端发现模式要求客户端负责查询注册中心,获取服务提供者的列表信息,使用负载均衡算法选择一个合适的服务提供者,发送请求。 服务端发现模式,客户端每次都请求注册中心,由注册中心内部选择一个合适的服务提供者,并将请求转发至该服务提供者,需要注意的是当一个请求过来的时候,注册中心内部获取服务提供者列表和使用负载均衡算法。
这两种服务发现模式有什么区别?
客户端服务发现和服务器端服务发现的区别如下。
① 客户端服务发现
在客户端模式下,如果要进行微服务调用,首先要进行的是到服务注册中心获取服务列表,然后再根据调用端本地的负载均衡策略,进行服务调用。
在上图中,client 端提供了负载均衡的功能,其首先从注册中心获取服务提供者的列表,然后通过自身负载均衡算法,选择一个最合理的服务提供者进行调用:
服务提供者向注册中心进行注册,提交自己的相关信息。 服务消费者定期从注册中心获取服务提供者列表。 服务消费者通过自身的负载均衡算法,在服务提供者列表里面选择一个合适的服务提供者进行访问。
客户端服务发现的优点:
负载均衡作为client中一个功能,用自身的算法,从服务提供者列表中选择一个合适服务提供者进行访问,因此client端可以定制化负载均衡算法。优点是服务客户端可以灵活、智能地制定负载均衡策略,包括轮询、加权轮询、一致性哈希等策略。 可以实现点对点的网状通讯,即去中心化的通讯。可以有效避开单点造成的性能瓶颈和可靠性下降等问题。 服务客户端通常以SDK的方式直接引入到项目,这种方式语言的整合程度最佳,程序执行性能最佳,程序错误排查更加容易。
客户端服务发现的缺点:
当负载均衡算法需要更新时候,很难做到同一时间全部更新,所以就造成新旧算法同时运行 与注册中心紧密耦合,如果要换注册中心,需要去修改代码,重新上线。微服务的规模越大,服务更新越困难,这在一定程度上违背了微服务架构提倡的技术独立性。
② 服务器端服务发现
在服务端模式下,调用方直接向服务注册中心进行请求,服务注册中心再通过自身负载均衡策略,对微服务进行调用。这个模式下,调用方不需要在自身节点维护服务发现逻辑以及服务注册信息。
在服务端模式下:
服务提供者向注册中心进行服务注册。 注册中心提供负载均衡功能。 服务消费者去请求注册中心,由注册中心根据服务提供列表的健康情况,选择合适的服务提供者供服务消费者调用。
现代容器化部署平台(如 Docker 和 Kubernetes)就是服务端服务发现模式的一个例子,这些部署平台都具有内置的服务注册表和服务发现机制。容器化部署平台为每个服务提供路由请求的能力。服务客户端向路由器(或者负载均衡器)发出请求,容器化部署平台自动将请求路由到目标服务一个可用的服务实例。因此,服务注册,服务发现和请求路由完全由容器化部署平台处理。
服务端发现模式的优点:
服务消费者不需要关心服务提供者的列表,以及其采取何种负载均衡策略。 负载均衡策略的改变,只需要注册中心修改就行,不会出现新老算法同时存在的现象。 服务提供者上下线,对于服务消费者来说无感知。
服务端发现模式的缺点:
运行时间增加,因为每次请求都要请求注册中心,由其返回一个服务提供者。 注册中心成为瓶颈,所有的请求都要经过注册中心,如果注册服务过多,服务消费者流量过大,可能会导致注册中心不可用。 微服务的一个目标是故障隔离,将整个系统切割为多个服务共同运行,如果某服务无法正常运行,只会影响到整个系统的相关部分功能,其它功能能够正常运行,即去中心化。然而,服务端发现模式实际上是集中式的做法,如果路由器或者负载均衡器无法提供服务,那么将导致整个系统瘫痪。
目前来说,大部分服务发现的实现都采取了客户端模式(sofapbrpc、brpc等)。但这个世界没有完美的架构和模式,不同的场景都有适合的解决方案。我们在调研决策的时候,一定要根据实际情况去权衡对比,选择最适合当前阶段的方案,然后通过渐进迭代的方式不断完善优化方案。
问题:服务注册有几种方式?
服务注册大致分为两类:
自注册模式:自注册,顾名思义,就是上述这些动作,有服务(client)本身来维护。每个服务启动后,需要到统一的服务注册中心进行注册登记,服务正常终止后,也可以到注册中心移除自身的注册记录。在服务执行过程中,通过不断的发送心跳信息,来通知注册中心,本服务运行正常。注册中心只要超过一定的时间没有收到心跳消息,就可以将这个服务状态判断为异常,进而移除该服务的注册记录。 三方注册模式:这个模式与自注册模式相比,区别就是健康检查的动作不是有服务本身(client)来负责,而是由其它第三方服务来确认。有时候服务自身发送心跳信息的方式并不精确,因为可能服务本身已经存在故障,某些接口功能不可用,但仍然可以不断的发送心跳信息,导致注册中心没有发觉该服务已经异常,从而源源不断的将流量打到已经异常的服务上来。这时候,要确认服务是否正常运转的健康检查机制,就不能只依靠心跳,必须通过其它第三方的验证(ping),不断的从外部来确认服务本身的健康状态。
什么是负载均衡?
负载均衡(Load Balance,简称 LB)是高并发、高可用系统必不可少的关键组件,目标是尽力将网络流量平均分发到多个服务器上,以提高系统整体的响应速度和可用性。
负载均衡的主要作用如下:
高并发:负载均衡通过算法调整负载,尽力均匀的分配应用集群中各节点的工作量,以此提高应用集群的并发处理能力(吞吐量)。 伸缩性:添加或减少服务器数量,然后由负载均衡进行分发控制。这使得应用集群具备伸缩性。 高可用:负载均衡器可以监控候选服务器,当服务器不可用时,自动跳过,将请求分发给可用的服务器。这使得应用集群具备高可用的特性。 安全防护:有些负载均衡软件或硬件提供了安全性功能,如:黑白名单处理、防火墙,防 DDos 攻击等。
负载均衡有几种类型?
负载均衡从硬件的层面可分为:硬件负载均衡和软件负载均衡。同时负载均衡从服务器的类型上又可分为:服务器负载均衡和客户端负载均衡。
① 硬件负载均衡
硬件负载均衡,一般是在定制处理器上运行的独立负载均衡服务器,价格昂贵,土豪专属。硬件负载均衡的主流产品有:F5 和 A10。
硬件负载均衡的优点:
功能强大:支持全局负载均衡并提供较全面的、复杂的负载均衡算法。 性能强悍:硬件负载均衡由于是在专用处理器上运行,因此吞吐量大,可支持单机百万以上的并发。 安全性高:往往具备防火墙,防 DDos 攻击等安全功能。
硬件负载均衡的缺点:
成本昂贵:购买和维护硬件负载均衡的成本都很高。 扩展性差:当访问量突增时,超过限度不能动态扩容。
② 软件负载均衡
软件负载均衡,应用最广泛,无论大公司还是小公司都会使用。软件负载均衡从软件层面实现负载均衡,一般可以在任何标准物理设备上运行。
软件负载均衡的主流产品有:Nginx、HAProxy、LVS(Linux Virtual Server)。
软件负载均衡的优点:
扩展性好:适应动态变化,可以通过添加软件负载均衡实例,动态扩展到超出初始容量的能力。 成本低廉:软件负载均衡可以在任何标准物理设备上运行,降低了购买和运维的成本。
软件负载均衡的缺点:
性能略差:相比于硬件负载均衡,软件负载均衡的性能要略低一些。
③ 服务端负载均衡
服务端负载均衡是指服务器端存储了所有可以调用服务的列表(通过心跳机制保证服务的可用性),然后客户端先访问负载均衡的服务器,服务器再通过负载均衡策略将请求转发到真正处理客户端服务的地址。
比如 Nginx 和 F5 都可以划分到服务端的负载均衡里面,后端的服务器地址列表是存储在后端服务器中或者存在专门的 Nginx 服务器或 F5 上。服务器的地址列表的来源是通过注册中心或者手动配置的方式来的。
④ 客户端负载均衡
客户端负载均衡是客户端自己维护了一份服务器的地址列表,再有客户端自己通过负载均衡策略来实现服务的调用,比如 Ribbon 就是客户端负载均衡框架。
客户端负载均衡器一般的实现原理是通过注册中心,如 Nacos,将可用的服务列表拉取到本地(客户端),再通过客户端负载均衡器(设置的负载均衡策略)获取到某个服务器的具体 IP 和端口,然后再通过 HTTP 框架请求服务并得到结果。
服务端负载均衡和客户端负载均衡有什么区别?
它们的区别主要体现在以下 2 点:
灵活性:服务器端负载均衡器是由运维人员把控和修改的,而客户端负载均衡是由开发团队灵活配置和修改的,并且服务器端的负载均衡策略只能所有调用者公用一套,而客户端负载均衡器可以设置多套,所以客户端负载均衡器更加灵活。 依赖注册中心:客户端负载均衡需要强依赖注册中心,通过注册中心来获取可用的服务列表,而服务器端负载均衡器则不需要依赖注册中心。
目前微服务框架使用的是客户端负载均衡器,而传统软件如 Nginx、F5 等使用的是服务器负载均衡器。
Ribbon 有哪些负载均衡策略?
Ribbon 内置了以下 7 种负载均衡策略。
1.轮询策略
轮询策略:RoundRobinRule,按照一定的顺序依次调用服务实例。比如一共有 3 个服务,第一次调用服务 1,第二次调用服务 2,第三次调用服务3,依次类推。
此策略的配置设置如下:
springcloud-nacos-provider:# nacos中的服务idribbon:NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RoundRobinRule #设置负载均衡
2.权重策略
权重策略:WeightedResponseTimeRule,根据每个服务提供者的响应时间分配一个权重,响应时间越长,权重越小,被选中的可能性也就越低。
它的实现原理是,刚开始使用轮询策略并开启一个计时器,每一段时间收集一次所有服务提供者的平均响应时间,然后再给每个服务提供者附上一个权重,权重越高被选中的概率也越大。
此策略的配置设置如下:
springcloud-nacos-provider:# nacos中的服务idribbon:NFLoadBalancerRuleClassName: com.netflix.loadbalancer.WeightedResponseTimeRule
3.随机策略
随机策略:RandomRule,从服务提供者的列表中随机选择一个服务实例。
此策略的配置设置如下:
springcloud-nacos-provider:# nacos中的服务idribbon:NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule #设置负载均衡
4.最小连接数策略
最小连接数策略:BestAvailableRule,也叫最小并发数策略,它是遍历服务提供者列表,选取连接数最小的⼀个服务实例。如果有相同的最小连接数,那么会调用轮询策略进行选取。
此策略的配置设置如下:
springcloud-nacos-provider:# nacos中的服务idribbon:NFLoadBalancerRuleClassName: com.netflix.loadbalancer.BestAvailableRule #设置负载均衡
5.重试策略
重试策略:RetryRule,按照轮询策略来获取服务,如果获取的服务实例为 null 或已经失效,则在指定的时间之内不断地进行重试来获取服务,如果超过指定时间依然没获取到服务实例则返回 null。
此策略的配置设置如下:
ribbon:ConnectTimeout: 2000# 请求连接的超时时间ReadTimeout: 5000# 请求处理的超时时间springcloud-nacos-provider:# nacos 中的服务 idribbon:NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule #设置负载均衡
6.可用性敏感策略
可用敏感性策略:AvailabilityFilteringRule,先过滤掉非健康的服务实例,然后再选择连接数较小的服务实例。
此策略的配置设置如下:
springcloud-nacos-provider:# nacos中的服务idribbon:NFLoadBalancerRuleClassName: com.netflix.loadbalancer.AvailabilityFilteringRule
7.区域敏感策略
区域敏感策略:ZoneAvoidanceRule,根据服务所在区域(zone)的性能和服务的可用性来选择服务实例,在没有区域的环境下,该策略和轮询策略类似。
此策略的配置设置如下:
springcloud-nacos-provider:# nacos中的服务idribbon:NFLoadBalancerRuleClassName: com.netflix.loadbalancer.ZoneAvoidanceRule
说一下什么是限流?
限流就是限制流量的意思,它是一种保护系统正常运行的机制。常见的限流场景有:
限制某个接口一分钟内最多请求 100 次 限制某个用户的下载速度最多 100KB/S 限制某个用户同时只能对某个接口发起 5 路请求 限制某个 IP 来源禁止访问任何请求
从上面的场景可以看出,根据不同的请求者和请求资源,可以组合出不同的限流规则。可以根据请求者的 IP 来进行限流,或者根据请求对应的用户来限流,又或者根据某个特定的请求参数来限流。而限流的对象可以是请求的频率,传输的速率,或者并发量等,其中最常见的两个限流对象是请求频率和并发量,他们对应的限流被称为请求频率限流(Request rate limiting)和并发量限流(Concurrent requests limiting)。
比如 Sentinel 中就提供了两种限流机制:QPS(每秒查询数)限流和并发数限流,其实对应的就是请求频次限流和并发量限流。
限流的处理方式有哪些?
常见的限流处理方式有以下 3 种:
拒绝服务 排队等待 服务降级
最简单的做法是拒绝服务,直接抛出异常,返回错误信息(比如返回 HTTP 状态码 429 Too Many Requests),或者给前端返回 302 重定向到一个错误页面,提示用户资源没有了或稍后再试。但是对于一些比较重要的接口不能直接拒绝,比如秒杀、下单等接口,我们既不希望用户请求太快,也不希望请求失败,这种情况一般会将请求放到一个消息队列中排队等待,消息队列可以起到削峰和限流的作用。第三种处理方式是服务降级,当触发限流条件时,直接返回兜底数据,比如查询商品库存的接口,可以默认返回有货。
限流实现算法有哪些?
限流的常见算法有:
固定窗口算法(Fixed Window) 滑动窗口算法(Rolling Window 或 Sliding Window) 漏桶算法(Leaky Bucket) 令牌桶算法(Token Bucket)
① 固定窗口算法(Fixed Window)
固定窗口算法是一种最简单的限流算法,它根据限流的条件,将请求时间映射到一个时间窗口,再使用计数器累加访问次数。譬如限流条件为每分钟 5 次,那么就按照分钟为单位映射时间窗口,假设一个请求时间为 11:00:45,时间窗口就是 11:00:00 ~ 11:00:59,在这个时间窗口内设定一个计数器,每来一个请求计数器加一,当这个时间窗口的计数器超过 5 时,就触发限流条件。当请求时间落在下一个时间窗口内时(11:01:00 ~ 11:01:59),上一个窗口的计数器失效,当前的计数器清零,重新开始计数。
计数器算法非常容易实现,在单机场景下可以使用 AtomicLong、LongAdder 或 Semaphore 来实现计数,而在分布式场景下可以通过 Redis 的 INCR 和 EXPIRE 等命令并结合 EVAL 或 lua 脚本来实现,Redis 官网提供了几种简单的实现方式。无论是请求频率限流还是并发量限流都可以使用这个算法。
不过这个算法的缺陷也比较明显,那就是存在严重的临界问题。由于每过一个时间窗口,计数器就会清零,这使得限流效果不够平滑,恶意用户可以利用这个特点绕过我们的限流规则。
② 滑动窗口算法(Rolling Window 或 Sliding Window)
为了解决固定窗口算法的临界问题,可以将时间窗口划分成更小的时间窗口,然后随着时间的滑动删除相应的小窗口,而不是直接滑过一个大窗口,这就是滑动窗口算法。我们为每个小时间窗口都设置一个计数器,大时间窗口的总请求次数就是每个小时间窗口的计数器的和。如下图所示,我们的时间窗口是 5 秒,可以按秒进行划分,将其划分成 5 个小窗口,时间每过一秒,时间窗口就滑过一秒。
每次处理请求时,都需要计算所有小时间窗口的计数器的和,考虑到性能问题,划分的小时间窗口不宜过多,譬如限流条件是每小时 N 个,可以按分钟划分为 60 个窗口,而不是按秒划分成 3600 个。当然如果不考虑性能问题,划分粒度越细,限流效果就越平滑。相反,如果划分粒度越粗,限流效果就越不精确,出现临界问题的可能性也就越大,当划分粒度为 1 时,滑动窗口算法就退化成了固定窗口算法。由于这两种算法都使用了计数器,所以也被称为计数器算法(Counters)。
③ 漏桶算法(Leaky Bucket)
除了计数器算法,另一个很自然的限流思路是将所有的请求缓存到一个队列中,然后按某个固定的速度慢慢处理,这其实就是漏桶算法(Leaky Bucket)。漏桶算法假设将请求装到一个桶中,桶的容量为 M,当桶满时,请求被丢弃。在桶的底部有一个洞,桶中的请求像水一样按固定的速度(每秒 r 个)漏出来。
桶的上面是个水龙头,我们的请求从水龙头流到桶中,水龙头流出的水速不定,有时快有时慢,这种忽快忽慢的流量叫做 Bursty flow。如果桶中的水满了,多余的水就会溢出去,相当于请求被丢弃。从桶底部漏出的水速是固定不变的,可以看出漏桶算法可以平滑请求的速率。
当请求到达时,不直接处理请求,而是将其放入一个队列,然后另一个线程以固定的速率从队列中读取请求并处理,从而达到限流的目的。注意的是这个队列可以有不同的实现方式,比如设置请求的存活时间,或将队列改造成 PriorityQueue,根据请求的优先级排序而不是先进先出。当然队列也有满的时候,如果队列已经满了,那么请求只能被丢弃了。漏桶算法有一个缺陷,在处理突发流量时效率很低,于是人们又想出了下面的令牌桶算法。
④ 令牌桶算法(Token Bucket)
令牌桶算法(Token Bucket)是目前应用最广泛的一种限流算法,它的基本思想由两部分组成:生成令牌和消费令牌。
生成令牌:假设有一个装令牌的桶,最多能装 M 个,然后按某个固定的速度(每秒 r 个)往桶中放入令牌,桶满时不再放入; 消费令牌:我们的每次请求都需要从桶中拿一个令牌才能放行,当桶中没有令牌时即触发限流,这时可以将请求放入一个缓冲队列中排队等待,或者直接拒绝。
在上面的图中,我们将请求放在一个缓冲队列中,可以看出这一部分的逻辑和漏桶算法几乎一模一样,只不过在处理请求上,一个是以固定速率处理,一个是从桶中获取令牌后才处理。
什么是 Sentinel?
Sentinel 是面向分布式、多语言异构化服务架构的流量治理组件,主要以流量为切入点,从流量路由、流量控制、流量整形、熔断降级、系统自适应过载保护、热点流量防护等多个维度来帮助开发者保障微服务的稳定性。
Sentinel 提供了哪些功能?
Sentinel 提供的主要功能有:
流量控制:流量控制主要指的是限流功能,当有突发流量时,通过限流可以防止程序繁忙甚至是挂掉的问题; 熔断降级:指程序在出现问题时,仍能保证有限功能可用的一种机制; 系统负载保护:当操作系统的某项指标,比如 CPU 使用率达到某个值之后,拒绝部分请求以实现保护系统的目的。
Sentinel 有什么优点?
Sentinel 的优点主要有以下几个:
丰富的应用场景:Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景,例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、实时熔断下游不可用应用等。 完备的实时监控:Sentinel 同时提供实时的监控功能。您可以在控制台中看到接入应用的单台机器秒级数据,甚至 500 台以下规模的集群的汇总运行情况。 广泛的开源生态:Sentinel 提供开箱即用的与其它开源框架/库的整合模块,例如与 Spring Cloud、Dubbo、gRPC 的整合。您只需要引入相应的依赖并进行简单的配置即可快速地接入 Sentinel。 完善的 SPI 扩展点:Sentinel 提供简单易用、完善的 SPI 扩展点。您可以通过实现扩展点,快速的定制逻辑。例如定制规则管理、适配数据源等。
Sentinel 有哪些核心组件?
Sentinel 的核心组件分为两部分:
核心库(Java 客户端):不依赖任何框架/库,能够运行于 Java 8 及以上的版本的运行时环境,同时对 Dubbo / Spring Cloud 等框架也有较好的支持。 控制台(Dashboard):Dashboard 主要负责管理推送规则、监控、管理机器信息等。
Sentinel 是如何运作的?
要了解 Sentinel 是如何运作的,需要先了解 Sentinel 中的两个基本概念:
资源:资源是 Sentinel 的关键概念。它可以是 Java 应用程序中的任何内容,例如,由应用程序提供的服务,或由应用程序调用的其它应用提供的服务,甚至可以是一段代码。在接下来的文档中,我们都会用资源来描述代码块。只要通过 Sentinel API 定义的代码,就是资源,能够被 Sentinel 保护起来。大部分情况下,可以使用方法签名,URL,甚至服务名称作为资源名来标示资源; 规则:围绕资源的实时状态设定的规则,可以包括流量控制规则、熔断降级规则以及系统保护规则。所有规则可以动态实时调整。
Sentinel 把(每个)接口视为资源,再通过配置的规则来控制资源的行为,如限流、熔断等行为。
什么是 Nacos?
Nacos 是 Dynamic Naming and Configuration Service 首字母缩写,它致力于帮助开发者发现、配置和管理微服务。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据及流量管理。同时它也是 Spring Cloud Alibaba 中的重要组件。
Nacos 提供了哪些功能?
Nacos 提供了以下几个重要的功能:
服务发现和服务健康监测:Nacos 支持基于 DNS 和基于 RPC 的服务发现。服务提供者使用原生 DK、OpenAPI、或一个独立的 Agent TODO 注册 Service 后,服务消费者可以使用 DNS TODO 或 HTTP&API 查找和发现服务。Nacos 提供对服务的实时的健康检测,阻止向不健康的主机或服务实例发送请求。Nacos 支持传输层 (PING 或 TCP)和应用层 (如 HTTP、MySQL、用户自定义)的健康检查。对于复杂的云环境和网络拓扑环境中(如 VPC、边缘网络等)服务的健康检查,Nacos 提供了 agent 上报模式和服务端主动检测 2 种健康检查模式。Nacos 还提供了统一的健康检查仪表盘,帮助您根据健康状态管理服务的可用性及流量; 动态配置服务:动态配置服务可以让您以中心化、外部化和动态化的方式管理所有环境的应用配置和服务配置。动态配置消除了配置变更时重新部署应用和服务的需要,让配置管理变得更加高效和敏捷。配置中心化管理让实现无状态服务变得更简单,让服务按需弹性扩展变得更容易。Nacos 提供了一个简洁易用的 UI (控制台样例 Demo) 帮助您管理所有的服务和应用的配置。Nacos 还提供包括配置版本跟踪、金丝雀发布、一键回滚配置以及客户端配置更新状态跟踪在内的一系列开箱即用的配置管理特性,帮助您更安全地在生产环境中管理配置变更和降低配置变更带来的风险; 动态 DNS 服务:动态 DNS 服务支持权重路由,让您更容易地实现中间层负载均衡、更灵活的路由策略、流量控制以及数据中心内网的简单 DNS 解析服务。动态 DNS 服务还能让您更容易地实现以 DNS 协议为基础的服务发现,以帮助您消除耦合到厂商私有服务发现 API 上的风险。Nacos 提供了一些简单的 DNS APIs TODO 帮助您管理服务的关联域名和可用的 IP:PORT 列表; 服务及其元数据管理:Nacos 能让您从微服务平台建设的视角管理数据中心的所有服务及元数据,包括管理服务的描述、生命周期、服务的静态依赖分析、服务的健康状态、服务的流量管理、路由及安全策略、服务的 SLA 以及最首要的 metrics 统计数据。
小结:挑重要的来说,一句话总结 Nacos 中的重要功能有两个:服务注册与发现 + 配置中心。
Nacos 生态如何?
Nacos 几乎支持所有主流语言,其中 Java/Golang/Python 已经支持 Nacos 2.0 长链接协议,能最大限度发挥 Nacos 性能。阿里微服务 DNS(Dubbo+Nacos+Spring-cloud-alibaba/Seata/Sentinel)最佳实践,是 Java 微服务生态最佳解决方案。
Nacos 组成部分有哪些?
Nacos 是由服务端(含页面控制台)和客户端程序(使用 Nacos 的程序)组成的,它里面包含了配置中心列表及管理功能、服务列表及管理功能和权限控制、集群管理等功能。
Nacos 部署方式有几种?
Nacos 支持 3 种部署模式:
单机部署:用于测试和开发环境试用。 集群部署:用于生产环境,确保高可用。 多集群部署:用于多数据中心场景。
Nacos 安装方式有以下 2 种:
使用源码编译安装。 使用已编译好的安装包进行安装。
如何启动 Nacos 服务器?
有安装包之后,先解压安装包,然后进入安装包的 bin 目录(cd nacos/bin),如果是测试和开发环境可以使用命令:sh startup.sh -m standalone,启动 Nacos 的单机模式进行使用。
等待 Nacos 启动好之后,在浏览器输入:http://localhost:8848/nacos/,进入Nacos 控制台的登录页面,之后输入默认用户/密码:nacos/nacos 进入管理页面。
关闭服务的话 Linux/Unix/Mac 使用命令:sh shutdown.sh,Windows 使用命令:shutdown.cmd 关闭,此命令需要在 nacos/bin 下执行。
Nacos 单机和集群部署有什么区别吗?
除了启动命令和部署个数不同之外,在数据库的依赖上 Nacos 单机和集群部署也是有区别的。
Nacos 单机是不依赖任何数据库的,虽然 Nacos 本身需要存储数据,但单机模式 Nacos 使用的是内置的 Derby 数据库,而 Nacos 依赖 MySQL 做数据存储。
Nacos 配置中心有什么优点?
配置中心的作用是将本地配置文件云端话,所谓的云端也就是 Nacos 的服务器端,这样既能保证配置文件中的敏感数据不会暴露,同时又提供了实时的修改、查看、回滚和动态刷新配置文件的功能,非常实用。
如何实现 Nacos 配置项的动态刷新?
在 Nacos 配置中心修改了配置文件,在不重启项目的前提下,可以实时读取到最新的配置内置。Nacos 默认会为所有获取数据成功的 Nacos 的配置项添加了监听功能,在监听到服务端配置发生变化时会实时触发 org.springframework.cloud.context.refresh.ContextRefresher 的 refresh 方法。但如果需要对 Bean 进行动态刷新,需要参照 Spring 和 Spring Cloud 规范,推荐给类添加 @RefreshScope 或 @ConfigurationProperties 注解,就可以实现配置中心的动态刷新功能了。
当客户端连接不到 Nacos 配置服务时会怎么?
Nacos 客户端有一个“配置快照”的功能,当 Nacos 客户端连接不到 Nacos 配置中心(Nacos 服务端)时,会使用本地的配置快照,类似于 Git 中的本地 commit,也类似于缓存,会在适当的时机更新,但是并没有缓存过期(expiration)的概念,当客户端无法连接到 Nacos Server 时,可以使用配置快照显示系统的整体容灾能力。
什么是注册中心?
注册中心是服务实例存储的仓库,也是服务提供者和服务消费者进行交互的桥梁。它主要提供了服务注册和服务发现这两大核心功能。
注册中心的特性是:可注册、能发现、高可用。
服务提供者将连接信息注册到注册中心,服务消费者去注册中心获取健康的服务提供者地址进行消费(访问),从而实现了服务提供者和服务消费者的解耦。所以注册中心需要包含以下 3 个角色:
提供者(Provider):服务的提供方(被调用方); 消费者(Consumer):服务的消费方(调用方); 注册中心(Registry):Nacos 注册中心,服务提供列表、消费关系等数据的存储方。
Nacos 有几种服务类型?
Nacos 中有两种服务类型:临时实例和永久实例。
临时实例只是临时存在于注册中心中,会在服务下线或不可用时被注册中心剔除,临时实例会与注册中心保持心跳,注册中心会在⼀段时间没有收到来自客户端的心跳后会将实例设置为不健康,然后在⼀段时间后进行剔除。 永久实例在被删除之前会永久的存在于注册中心,且有可能并不知道注册中心存在,不会主动向注册中心上报心跳,那么这个时候就需要注册中心主动进行探活。
说一下 Nacos 中的健康检测机制?
Nacos 中的健康检测机制是用来检查服务健康状态的,只有健康的节点才会被服务消费端调用,这样才能保证程序稳定、正常的运行。Nacos 中提供了两种健康检测的机制:
客户端主动上报(健康状态的)机制。 服务器端反向探测(健康状态的)机制。
如何理解这两种机制呢?
想象⼀下这么⼀个场景,你所在的地区突然发生地质灾害,你被掩盖在废墟下面,搜救队必须要知道你在废墟里面,那么才能对你进行施救。那有什么方法可以让救援队知道你在废墟下面?
第⼀种,你在废墟里面大喊 help! help! I am here! ,让搜救队知道你的位置和健康状态。 第二种,搜救队使用了他们的专业检查设备,探测到你正埋在废墟下面。
以上这两种方法和 Nacos 的两种健康检测机制类似,也就是客户端主动上报机制,是客户端每隔一段时间,主动向 Nacos 服务器端上报自己的健康状况,而服务器端反向探测机制是 Nacos 服务器端来检测客户端是否健康。
Nacos 中的健康检测机制不能主动设置,但健康检测机制是和 Nacos 的服务实例类型强相关的。
也就是说 Nacos 中的两种服务实例分别对应了两种健康检查机制:
临时实例(也可以叫做非持久化实例):对应的是客户端主动上报机制。 永久实例(也可以叫做持久化实例):服务端反向探测机制。
什么是保护阈值?
保护阈值(ProtectThreshold)是为了防止因过多实例故障,导致所有流量全部流入剩余健康实例,继而造成流量压力将剩余健康实例被压垮形成雪崩效应。应将健康保护阈值定义为⼀个 0 到 1 之间的浮点数(它的默认值为 0)。当域名健康实例数占总服务实例数的比例小于该值时,无论实例是否健康,都会将这个(健康或不健康的)实例返回给客户端。这样做虽然损失了⼀部分流量,但是保证了集群中剩余健康实例能正常工作。
比如某个服务有 100 个实例,其中 99 个实例都宕机了,只剩下一个健康的实例,这个时候如果把所有的请求都转发到这一个健康实例上就会造成雪崩效应,最终导致业务系统崩溃。
为了防止这种极端情况,于是就有了“保护阈值”,保护阈值一旦被触发,那么 Nacos 将会把请求转发给所有服务实例,也就是健康实例+非健康实例,这样可能会损失了⼀部分流量,但能保证集群中剩余的健康实例能正常工作。
Nacos 有负载均衡吗?
Nacos 0.7.0 版本中提供了负载均衡功能,它的负载均衡策略主要有两个:
基于权重的负载均衡,权重的值可以由用户来设置和修改 负载均衡支持就近访问,服务同地域优先访问
Nacos 是 AP 模式还是 CP 模式?
CAP 含义如下:
C:Consisteny(一致性),比如数据库是主从模式,一个写库请求进来了,主库完成了写入操作,但是再(从库)同步数据之前,另一个用户查了这条数据,结果没查到,但是也没报错,这就不是强一致性。虽然最终会同步成功,但这是最终一致性的体现。强一致性的体现在于我不管你因为什么没同步成功(可能网络延迟或其他等),只要没同步成功,我这个从库就不能对外提供服务。必须主从数据一致才可以提供服务。 A:Availability(可用性),还是上面的例子,就是保证了可用性。因为虽然主从没同步完成,但是我从库照样能提供服务而且及时响应结果。也就是说可用性保证服务可用,而不在乎数据是否一致。 P:Partition Tolerance(分区容错性),集群部署了三台服务,挂了一台,其他两台还能继续对外提供服务,这时候我就认为他是没问题的,也就是我能容忍你挂了一台,只要还有服务能对外提供请求即可。所以一般分区容忍性是必须的,一般都需要从 C 和 A 之间做选择。
所以 CP 模式既一致性和分区容忍性,而 AP 模式即可用性和分区容忍性。
Nacos 注册中心默认是 AP 模式,但同时也支持 CP 模式,所以 Nacos 中包含了 AP 和 CP 两种模式。