面经 · 2024年3月31日 0

API 项目面经(持续更新)

项目是你自己做的吗?你为什么做这样的一个项目?你做这个项目的背景(初衷)是什么?几乎每次都被问到

我的初衷是尽可能地帮助和服务更多的用户和开发者,让他们更加方便快捷地获取他们想要的信息和功能。

接口开放平台它可以帮助开发者快速接入一些常用的服务,从而提高他们的开发效率,比如天气服务、随机头像和心灵鸡汤等服务,它们是一些应用或者小程序中常见的功能,所以提供这些接口可以帮助开发者更加方便地实现这些功能。

这些接口也可以让用户在使用应用时获得更加全面的功能和服务,从而提高他们的用户体验。所以我认为接口开放平台是一个有意义的项目,可以为用户和开发者带来更多的便利和价值。

注:因为我个人已经将项目上线,并能够提供一些真实的接口服务。有条件的同学尽量将项目上线。此外有两场的面试官想要查看数据库,我开了屏幕共享给他们看,所以要对数据库的表结构和设计有一定的了解。

项目的架构你是怎么设计的?

我采用前后端分离的架构,前端使用Nginx部署,通过 Nginx 反向代理将请求转发到 web 项目,因为项目刚刚上线,所以这里暂时采用了单机部署的模式,未来可能采取水平扩容的方式,增加多台节点,通过 Nginx 的负载均衡,将请求平均的分发到我的每个节点上,以支撑更高的并发。

我的 web 项目使用 Spring Boot 开发,并连接到了数据库和 Redis,数据库使用的是 MySQL,主要用来存储用户的信息和接口的信息;通过 Redis 实现了分布式 session,因为考虑到未来要使用分布式架构,为了避免使用 tomcat 保存 session 有用户登录失效的问题。

注:这里我说出了反向代理,水平扩容,负载均衡等技术名词,很多面试官会根据这些名词进行延伸提问(引导面试官往自己熟悉的东西上提问)比如:说说什么是正向代理/反向代理?什么是水平扩容?什么是负载均衡?你了解哪些负载均衡的算法?提前准备好这些知识之后,就可以跟面试官一顿输出了。

请介绍一下本项目的完整业务流程?

假设有一个获取今日天气的接口,管理员可以在 API 开放平台创建并发布该接口。

用户在注册登录后,会分配一对接口调用签名(ak / sk),用户可以在网站上浏览接口文档并进行在线调试,还可以使用客户端 SDK 轻松地在自己的代码中调用接口。

系统后端会校验用户签名的合法性,并统计接口的调用次数,管理员可以在后台查看分析各接口的调用情况。

你的开发流程是什么?先实现还是先技术选型?

这里可以参考鱼皮大佬直播开发时对企业中做项目流程的讲解

我先参考了一些已有的产品,根据这些产品,总结出来比较好的功能点,再结合自己想要实现的一些功能特色,去做了一个项目整体设计,有了产品原型后再进行技术选型使用什么样的技术去解决什么样的业务问题。

为什么你要使用网关?

我这个平台的关键点就在于提供接口服务,要保证接口的可用性和稳定性,所以将接口服务独立部署在另一台机器上,隐藏真实的接口地址及端口,调用接口服务的请求都必须经过网关流量染色之后…(这里细节太多,比如 rpc 调用获取用户sk,重新生成签名认证等等)之后,将请求转发到真实的接口地址,防止接口被恶意调用、盗刷。

注:这个问题要对网关做了什么事情非常非常熟悉,建议反复观看鱼皮大佬的直播回放。

为什么使用 RPC 调用?有了解过其他的方式吗?

因为如果在网关引入数据库的操作的话,不仅会增加项目体积,以及违背了设计原则的单一职责原则,所以我考虑通过服务间调用的方式,我了解过有两种方式,第一种是 Open feign,原理是构造了一个 HTTP 请求,并会添加很多的请求头,body 是使用 json 字符串传输,所以调用效率会比较低,更加适合外部服务间的调用。

然后我了解到 RPC 是可以基于 TCP 协议,避免了无用的请求头,以及可以通过将数据序列化为二进制流的形式传输效率更加高效,更加安全,所以更适用于我这个场景。最终我选择了 Dubbo RPC 框架来实现这个功能。

你的接口调用次数统计以及排行是怎么实现的?

答:通过 MySQL 统计,每次调用结束后,网关都会发起一个 rpc 请求,调用次数+1。

注:这里我会抛出一个设计缺陷,在实际测试过程中,通过 jmeter 压测工具,会出现调用次数不准的情况,原因是因为没有在业务层面加锁,导致数据库出现并发写的问题。并且并发量大的话,对数据库造成很大的压力。引导面试官问出,那你有什么更好的解决方案吗?

答:如果在业务层面加一个写锁的话,会影响业务的执行效率,所以我想使用 Redis 去解决,Redis 有一个数据结构 Zset 支持排序,score 可以用来存储调用次数,并且 Redis 是单线程,可以解决并发问题。

注:这里被追问过 Zset 的底层实现,以及如何将这些数据进行持久化保存,防止 Redis 宕机导致数据丢失,可以从 AOF,RDB 展开来讲,或者在后台开启一个定时任务,定时将这些数据进行落库。

你做过什么优化吗?你接口的性能怎么样?

答:我有一个接口是随机返回土味情话,我在数据库中插入了几千条土味情话,当调用接口时随机返回一条。

在还没有优化前,接口的 qps 在 300 左右,但是考虑到这个接口只有读操作,没有增删改操作,所以我将这张表的存储引擎从 Innodb 改为了 MyISAM,接口的 qps 提升到了 1500

注:被面试官追问为什么改为 MyISAM 有这么大的性能提升?Innodb 和 MyISAM 有什么区别?

这个问题一定要根据自己实际情况来答,根据自己擅长的方面,比如对查询语句做了索引优化,提升了接口的性能。

在开发过程中,你遇到过比较复杂的技术问题或挑战吗?如果有,请谈谈你是如何解决这些问题的?

比如你在设计实现 API 签名认证算法时,发现鉴权结果和预期不同 —— ak、sk 明明完全一致,但就是无法通过鉴权。然后你通过 debug 的方式,定位到了关键问题是由于中文编码、或者字段大小写不一致导致了签名不同。最后你通过修改中文编码等方式确保客户端和服务端生成的签名一致进行解决。

你的项目中使用了哪些技术栈?请分别介绍一下 Spring Boot、Dubbo、Gateway 在项目中的作用

项目技术栈:SSM + Spring Boot、Spring Cloud Gateway、Dubbo、Nacos、MySQL、Redis、MyBatis-Plus、Hutool 工具库。

Spring Boot:用于快速构建基础的后端项目,只需要修改配置文件,就能轻松整合 SSM、MySQL、Redis 等依赖。

Dubbo:分布式 RPC 框架,实现项目中不同模块的高性能相互调用,比如网关服务集中统计接口调用次数时通过 Dubbo 调用接口服务完成次数扣减。

Gateway:作为 API 网关,集中接受客户端的请求,并执行统一的安全认证、请求转发、流量控制、请求日志、公共业务等操作。

使用 MySQL 的原因是因为考虑到未来有用户充值交易,限制调用次数等场景需要用事务保证数据的完整性和一致性

使用 Redis 的原因是因为可以用来实现分布式 session、锁、缓存等功能。因为 Redis 是一个单独的中间件,不同客户端可以往同一个 Redis 或者集群中存放session/加锁,这样就能保证资源能够在分布式服务下都可见

并且由于 Redis 也是单线程的,同时也支持 lua 脚本,可以保证并发安全的问题,所以可以很简单的实现分布式锁的功能。

注:被面试官追问自动装配的原理你了解过吗?自动装配是怎么实现的?分布式 session 的原理?

你将后端项目划分为了多个子项目,请分别介绍这几个子项目的作用、以及它们之间是如何协作和交互的?

API 开放平台分为 5 个子项目(核心模块),分别为:

  • api-backend:核心业务后端,负责用户和接口管理等核心业务功能
  • api-gateway:API 网关服务,负责集中的路由转发、统一鉴权、统一业务处理、访问控制等
  • api-common:公共模块,包括各其他模块中需要复用的方法、工具类、实体类、全局异常等
  • api-client-sdk:客户端 SDK,封装了对各 API 接口的调用方法,降低开发者的使用成本。
  • api-interface:提供模拟 API 接口

交互流程:首先管理员创建接口后通过核心业务后端(api-backend)保存到数据库中。用户调用某个接口时,在自己的项目中引入客户端 SDK(api-client-sdk)并通过一行代码发起调用,请求会首先发送到 API 网关(api-gateway)进行用户的鉴权和接口调用统计,然后将请求转发到实际的 API 接口(api-interface)。

请简要介绍 Maven 的基本概念、作用以及如何使用 Maven 进行多模块依赖管理和打包?

Maven 是一个开源的构建工具,用于管理 Java 项目的构建、依赖管理和项目生命周期。

本项目的所有依赖都是由 Maven 进行管理的,每个子项目都有自己的 pom.xml 进行管理。首先使用 mvn install 命令将 common 公共模块在本地打包,然后在其他子项目的 pom.xml 中引入该模块即可复用代码。每个子项目可以独立通过 mvn package 命令进行打包和部署。

Maven 还支持子父依赖多模块管理,通过 modules 配置给父项目指定子模块,从而实现统一的公共依赖和依赖版本定义。

请介绍一下你是如何使用 MyBatis Plus 框架的 QueryWrapper 实现了对 MySQL 的灵活查询?

MyBatis-Plus 是 MyBatis 的增强版框架,允许用户通过编程的方式构建复杂的查询条件,无需编写繁琐的 SQL 语句。

在本项目中,使用 MyBatis-Plus 的 QueryWrapper 查询条件构造器,通过链式调用的方式,灵活构造接口信息表的查询条件,比如使用 like 方法指定根据描述模糊查询、比如 orderBy 指定查询排序规则等,示例代码如下:

QueryWrapper<InterfaceInfo> qw = new QueryWrapper<>();
qw.like(StringUtils.isNotBlank(description), "description", description);
qw.orderBy(StringUtils.isNotBlank(sortField),
        sortOrder.equals(CommonConstant.SORT_ORDER_ASC),
        sortField);

除了 QueryWrapper 外,MyBatis-Plus 还提供了 LambdaQueryWrapper,支持使用 Lambda 表达式来定义查询条件,更灵活。

什么是 OpenAPI 规范?它有什么作用或好处?

OpenAPI 规范是一种用于定义和描述 API 的开放标准,比如用 JSON 来描述 API 的请求地址、请求参数、请求类型、响应信息等。

通过遵循 OpenAPI 规范的接口描述信息,我们可以实现接口文档的自动生成、客户端和服务端代码的自动生成等,提高开发测试效率。

在项目中,我使用 Knife4j + Swagger 自动生成 OpenAPI 规范的接口定义和可交互的接口文档,无需手动编写接口文档;并且在前端通过 Umi OpenAPI 插件根据 OpenAPI 接口文档自动生成了请求后端的代码,大幅提高开发效率。

你在项目中使用了 Swagger + Knife4j 自动生成接口文档,请谈谈 Swagger 和 Knife4j 的作用和它们对项目开发的影响。

Swagger 是一个用于自动构建和生成可交互接口文档的工具集。使用 Swagger 接口文档生成工具后,我不需要在开发完项目后手动编写一套接口文档,而是直接交由系统自动根据 Controller 接口层的代码自动生成文档,大幅节省时间。

使用 Swagger 生成的接口文档不仅能够分组查看请求参数和响应,还支持灵活的在线调试,可以直接通过界面发送请求来测试接口,提高开发调试效率。

此外,引入 Swagger 后,可以得到基于 OpenAPI 规范的接口定义 JSON,可以配合第三方工具来根据 JSON 自动生成前端请求代码、自动生成客户端调用 SDK 等。

Knife4j 是 Swagger 的增强版,能够生成更美观的 API 接口文档,并且提供了离线文档导出、接口分组排序等增强功能。(参考官网:https://doc.xiaominfo.com/docs/features

什么是 API 签名认证算法?它有什么作用?你又是如何实现它的?

API 签名认证算法是一种用于验证 API 请求的合法性和完整性的安全机制。

给接口使用 API 签名认证算法,可以增强 API 的安全性,防止未经授权的用户访问、防止恶意用户篡改请求数据。

实现步骤如下:

  1. 生成密钥对:给每个用户生成唯一的密钥对(accessKey 和 secretKey),并保存到数据库中,仅用户本人可查看自己的密钥对。
  2. 请求方生成签名: 请求方(客户端)使用 secretKey 对请求参数和数据进行签名,签名的内容包括请求参数、时间戳、随机数等,签名加密算法此处选择 MD5。
  3. 请求方发送请求:请求方将请求参数、签名、用户标识一起发送给 API 提供者,通常会把签名等元信息放到请求头参数中传递,注意千万不要传递 secretKey。
  4. API 提供者验证签名:在 API 网关中,通过请求头获取到用户标识,根据标识到数据库中查找该用户对应的 accessKey 和 secretKey,并使用相同的签名算法生成签名,和请求中的签名进行比对,如果签名一致,则 API 提供者可以信任请求方,可以进行后续操作。

    你在项目中使用了 Spring Cloud Gateway 作为 API 网关,请解释一下 API 网关的应用场景,以及它在项目中的实际应用?

API 网关的主要应用场景:路由转发、统一鉴权认证、负载均衡、访问控制、流量染色、集中限流、统一监控和日志记录、全局跨域解决等。

在本项目中,使用 API 网关:

1)统一鉴权认证:应用 API 签名认证算法校验用户请求的合法性

2)公共业务逻辑:对每个接口的调用进行集中的统计

3)路由转发:前端发送请求到 API 网关,通过网关转发到实际的 API 接口

4)流量染色:给经过网关的请求加上特定的请求头参数,便于让实际的 API 服务确定请求来源及合法性

你是如何基于 Spring Boot Starter 开发了客户端 SDK 的,讲述一下实现过程?

0)首先明确客户端 SDK 的定位和功能,不要把 SDK 设计得过于繁重

1)引入相关依赖。如 spring-boot-configuration-processor、spring-boot-autoconfigure 等,用于开启自动导入以及给出配置文件的编辑提示

2)编写配置类,用于创建一个客户端 Bean 对象。给配置类添加@ConfigurationProperties(prefix = "yuapi.client") 注解,用于自动从 Spring Boot 配置文件中读取配置。

3)注册配置类。在 resources/META_INF/spring.factories 文件中填写自动加载的配置类包路径

4)开发 SDK。像开发 Spring Boot 业务系统一样编写 SDK 功能代码

5)使用 SDK。在本地用 mvn install 命令打包 SDK,其他本地项目引入 SDK 即可使用

6)发布 SDK。在 Maven 中央仓库发布 SDK 包,其他开发者可通过 Maven 包索引在自己的项目中引入 SDK 并使用。

用户如何使用你开发的客户端 SDK?讲述一下流程。

1)在 API 开放平台进行注册登录,获取到开发者密钥 ak、sk

2)下载 SDK 代码到本地,或者从 Maven 中央仓库引入 pom 依赖

3)在项目的 application.yml 配置文件中填写客户端配置,比如 ak、sk 等

4)项目启动时,会自动创建一个客户端调用对象,可以直接在项目中注入该对象并使用

有哪些客户端 SDK 的设计技巧?

客户端 SDK 的目的是帮助开发者更轻松地使用我们系统提供的功能,因此在设计 SDK 时,要从开发者出发,提升开发者的调用体验。

可以从易用性、可理解性、可扩展性、高效稳定几个角度出发,多结合自己开发 SDK 的经历去回答,具体请见这篇文章:大厂 SDK 设计技巧

什么是 RPC?为什么要使用 Dubbo RPC 框架,它有什么优势?

RPC(Remote Procedure Call,远程过程调用)是一种用于实现分布式系统通信的协议和技术。它允许一个计算机程序调用另一个地址的函数或方法,就像本地函数调用一样,而不需要开发者显式地处理底层网络通信和数据序列化等问题。
Dubbo 是基于 Java 的高性能、轻量级的开源 RPC 框架,便于开发者轻松实现分布式系统和微服务架构。此外,Dubbo 还提供了服务治理等功能。
Dubbo RPC 框架的优势,简单来说就是性能高、协议多、功能强、生态好、易扩展。
具体的优势如下:

  1. 性能优秀:Dubbo 经过高度优化,具有出色的性能表现,适用于高并发和低延迟的场景。
  2. 多协议支持:Dubbo 支持多种通信协议,可以根据不同的需求选择合适的协议,提供灵活性。
  3. 服务治理:Dubbo 提供了丰富的服务治理功能,包括负载均衡、路由、容错处理等,有助于构建可靠的分布式系统。
  4. 生态系统:Dubbo 有广泛的生态系统和社区支持,提供了大量扩展和插件,满足各种应用场景的需求。
  5. 可扩展性:Dubbo 的架构设计允许开发者轻松扩展和定制功能,以适应不同的业务需求。

你在项目中是如何使用 Dubbo RPC 框架的,讲述一下使用流程?

在正式运用 Dubbo 到项目前,我先阅读了 Dubbo 的官方文档,按照快速启动文档跑通了基础的 RPC 调用 Demo,明确了注册中心、Maven 包等各依赖的版本号。

先在本地启动 Nacos 注册中心,然后在服务提供者和服务调用者项目引入 Dubbo 依赖(尽量引入相同的依赖和配置)、编写 Nacos 的连接配置、并且在项目启动类通过 @EnableDubbo 注解开启 Dubbo 支持。

编写服务提供者和服务调用客户端类,分别加上 @DubboService 和 @DubboReference 注解。

优先启动服务提供者项目,在 Nacos 控制台观察到服务注册信息,再启动服务调用者项目。

你在公共模块中抽象了模型层和业务层代码,请解释一下模型层和业务层的概念,并说明抽象公共模块的目的和好处。

模型层(Model):包括数据模型、实体类、业务封装对象等,一般不包含业务逻辑。

业务层(Service):包含了应用程序的业务逻辑和处理规则,一般会用到模型层的代码。

抽象公共模块的主要目的是为了复用代码。尤其是在微服务项目中,通常要把独立于业务的请求响应封装对象、全局异常处理类、常量、公共的数据模型抽象为公共模块,提供给各业务服务引入,便于项目的维护和理解。

你通过 API 网关实现了流量染色技术,请介绍一下流量染色的概念、以及它的作用?

流量染色是指根据请求的属性对请求进行分类和标记,从而进行特定的处理。

3 个关键概念:

1)请求分类:在请求层面的流量染色中,将请求分为不同的类别或组,通常基于请求的特性、内容、来源、用户身份等因素来进行分类。

2)请求标记:每个请求被标记为属于特定的类别或组,这个标记可以是请求头中的特定字段、请求参数、或其他识别请求的方式。

3)处理策略:为每个请求类别定义特定的处理策略,包括资源分配、访问控制、限流、缓存策略、安全性等。

在本项目中,所有的外部请求都要先经过 Gateway 网关,由网关给请求加上特定的请求头参数(比如 Source = MyAPI),便于让下游的 API 服务确定请求来源及合法性。