架构师如何理解和应用系统的非功能性需求

架构师如何理解和应用系统的非功能性需求

在软件开发中,我们往往很关注系统的功能性需求,也就是系统要实现哪些具体的业务功能。然而,对于一个高质量、能够稳定运行并满足用户期望的系统来说,非功能性需求同样起着举足轻重的作用。作为架构师,深入理解并合理应用这些非功能性需求,是构建健壮、高效系统的关键所在。本文将围绕性能要求、可扩展性、可用性、可靠性、安全性、可监控性与容错性等几个重要的非功能性需求方面展开讨论,通过源码解读、原理分析以及实际应用场景和代码示例来详细阐述如何在Java 系统中对它们进行有效的把控与实现。

一、性能要求

(一)理解性能要求

  • 性能要求关乎系统在响应时间、吞吐量、资源利用率等方面的表现。例如,一个电商系统在促销活动期间,要能够快速响应大量用户的并发请求,确保商品浏览、下单等操作流畅进行,这就对系统的响应时间和吞吐量提出了严格要求。

(二)实现原理

  • 优化算法与数据结构选择:合适的算法和数据结构能极大地影响性能。比如,在查找操作频繁的场景中,使用 HashMap(平均时间复杂度为 )相较于 LinkedList(查找时间复杂度为 )效率更高。以下是简单示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
import java.util.HashMap;
import java.util.Map;

public class DataLookup {
public static void main(String[] args) {
Map<String, Integer> dataMap = new HashMap<>();
dataMap.put("key1", 1);
dataMap.put("key2", 2);
// 快速查找元素,时间复杂度接近常数级
Integer value = dataMap.get("key1");
System.out.println(value);
}
}
  • 多线程与并发控制:利用多线程可以提高系统的并行处理能力,但需要合理的并发控制机制,如使用 synchronized 关键字或者 java.util.concurrent 包下的锁机制(如 ReentrantLock)来避免线程安全问题并提升性能。示例代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import java.util.concurrent.locks.ReentrantLock;

public class Counter {
private int count = 0;
private ReentrantLock lock = new ReentrantLock();

public void increment() {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}

public int getCount() {
return count;
}
}

上述代码通过 ReentrantLock 保证了在多线程环境下对 count 变量自增操作的原子性,避免了并发访问导致的数据不一致问题,同时能充分利用多核 CPU 的优势提升性能。

(三)应用场景

  • 高并发的 Web 应用:像在线支付系统、大型社交平台等,需要处理海量用户的并发请求,通过上述优化算法、数据结构以及合理的并发控制机制,确保系统在高负载下仍能快速响应。
  • 大数据处理系统:在数据批量处理、分析等场景中,选择高效的算法和数据结构(如使用 ArrayList 进行数据的顺序存储和快速遍历,结合合适的排序算法如快速排序等提高排序效率),提升整体的数据处理速度。

(四)性能测试与调优

  • 通过性能测试工具(如 JMeter、Apache Bench 等)模拟不同负载情况,收集系统响应时间、吞吐量、资源占用等指标数据,然后根据测试结果进行针对性的调优。例如,如果发现某个接口在高并发下响应时间过长,通过分析代码可能发现是频繁创建数据库连接导致的性能瓶颈,那么可以引入数据库连接池(如 HikariCP)来优化,以下是简单配置使用示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;

public class DatabaseConnectionExample {
private static final DataSource dataSource;

static {
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:mysql://localhost:3306/mydb");
config.setUsername("root");
config.setPassword("password");
// 配置连接池相关参数,如最大连接数、最小连接数等
config.setMaximumPoolSize(10);
config.setMinimumIdle(5);
dataSource = new HikariDataSource(config);
}

public static Connection getConnection() throws SQLException {
return dataSource.getConnection();
}
}

通过连接池预先创建并管理一定数量的数据库连接,避免每次请求都创建和销毁连接带来的开销,显著提升数据库访问性能。

二、可扩展性

(一)理解可扩展性

  • 可扩展性意味着系统能够方便地应对业务增长、功能扩展以及用户量增加等情况,无需对现有架构进行大规模的重构。例如,一个电商平台后续要增加新的商品分类、营销活动类型等功能时,系统架构应能轻松支持这些扩展。

(二)实现原理

  • 模块化与分层架构设计:将系统划分为不同的模块(如业务逻辑层、数据访问层、表示层等),各模块职责明确,通过接口进行交互。以常见的 Java Web 应用的三层架构为例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 表示层(例如使用Spring MVC框架)
@Controller
@RequestMapping("/orders")
public class OrderController {
private OrderService orderService;

// 通过依赖注入获取业务逻辑层服务
@Autowired
public OrderController(OrderService orderService) {
this.orderService = orderService;
}

@RequestMapping("/create")
public String createOrder(Order order) {
// 调用业务逻辑层方法创建订单
orderService.createOrder(order);
return "success";
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 业务逻辑层
@Service
public class OrderService {
private OrderRepository orderRepository;

@Autowired
public OrderService(OrderRepository orderRepository) {
this.orderRepository = orderRepository;
}

public void createOrder(Order order) {
// 业务逻辑处理,如验证订单信息等
// 调用数据访问层方法保存订单
orderRepository.save(order);
}
}
1
2
3
4
5
// 数据访问层(例如使用Spring Data JPA)
@Repository
public interface OrderRepository extends JpaRepository<Order, Long> {
// 继承通用的数据操作方法,也可自定义扩展方法
}

这种分层架构使得每个层次可以独立发展和扩展,比如要添加新的订单相关业务逻辑,只需在 OrderService 类中添加相应方法,不会影响到表示层和数据访问层的原有代码。

  • 微服务架构应用:将系统拆分成多个微服务,每个微服务独立部署、运行,通过轻量级的通信机制(如 RESTful API、消息队列等)进行交互。例如,一个电商系统可以拆分为用户服务、商品服务、订单服务等微服务。以用户服务为例,使用 Spring Boot 构建的简单微服务如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import org.springframework.boot.SpringBootApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
@RestController
public class UserServiceApplication {

public static void main(String[] args) {
SpringBootApplication.run(UserServiceApplication.class, args);
}

@GetMapping("/user")
public String getUser() {
return "User information";
}
}

当业务扩展时,各个微服务可以根据自身的负载情况进行水平扩展(增加实例数量),而且不同微服务可以采用不同的技术栈进行开发,灵活性极高。

(三)应用场景

  • 企业级应用系统升级:随着企业业务的不断拓展,如增加新的业务流程、产品线等,通过模块化和微服务架构的可扩展性优势,能快速在现有系统基础上进行功能扩展和整合。
  • 互联网平台功能迭代:像在线教育平台要不断增加新的课程类型、教学互动功能等,利用可扩展性好的架构可以高效地实现功能的更新和扩展,满足用户日益增长的需求。

三、可用性

(一)理解可用性

  • 可用性指系统在规定的条件下和规定的时间内完成规定功能的能力,通常用系统正常运行时间的百分比来衡量。例如,要求一个关键业务系统的可用性达到 99.99%,意味着每年的停机时间不能超过 52.6 分钟。

(二)实现原理

  • 负载均衡:通过负载均衡器(如 Nginx、HAProxy 等)将请求均匀地分发到多个后端服务器实例上,避免单点故障并提高系统整体的处理能力。以下是使用 Nginx 作为反向代理和负载均衡器的简单配置示例(配置文件部分内容):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
http {
upstream backend {
server backend1.example.com;
server backend2.example.com;
server backend3.example.com;
}

server {
listen 80;
server_name example.com;

location / {
proxy_pass http://backend;
}
}
}

上述配置将来自客户端访问 example.com 的请求,按照一定的负载均衡算法(如轮询、加权轮询等)分发到 backend1.example.com、backend2.example.com、backend3.example.com 这几个后端服务器上,当某个后端服务器出现故障时,其他服务器依然可以继续处理请求,保障系统的可用性。

  • 集群与冗余部署:部署多个相同功能的服务器节点组成集群,并且对关键组件(如数据库)进行冗余配置(如主从复制、多副本存储等)。以 MySQL 数据库的主从复制为例,在主服务器上配置:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
-- 在主服务器的配置文件中(my.cnf)添加以下配置,开启二进制日志
log-bin=mysql-bin
server-id=1

-- 创建用于从服务器连接的用户并授权
CREATE USER'repl'@'%' IDENTIFIED BY 'password';
GRANT REPLICATION SLAVE ON *.* TO'repl'@'%';

-- 查看主服务器状态,记录下 File 和 Position 的值
SHOW MASTER STATUS;
在从服务器上配置:
sql
-- 在从服务器的配置文件中(my.cnf)设置服务器 ID
server-id=2

-- 配置连接主服务器进行复制
CHANGE MASTER TO
MASTER_HOST='master_server_ip',
MASTER_USER='repl',
MASTER_PASSWORD='password',
MASTER_LOG_FILE='mysql-bin.000001',
MASTER_LOG_POS=1234;

-- 启动从服务器的复制线程
START SLAVE;

通过这样的主从复制机制,当主数据库出现故障时,可以快速切换到从数据库继续提供服务,保证数据的可用性以及系统的正常运行。

(三)应用场景

  • 电商购物平台:尤其是在购物高峰期(如 “双 11”“618” 等促销活动期间),通过负载均衡和集群部署,确保大量用户能够顺畅地进行商品浏览、下单等操作,保障系统的高可用性。
  • 金融交易系统:对可用性要求极高,任何短暂的停机都可能造成重大损失,所以采用冗余的服务器、数据库备份等措施来保证系统随时可用,满足客户的交易需求。

四、可靠性

(一)理解可靠性

可靠性强调系统在面对各种异常情况(如硬件故障、软件错误、网络问题等)时,仍能保持正确运行并提供预期功能的特性。

(二)实现原理

  • 事务管理:在数据库操作以及涉及多个相关操作的业务场景中,使用事务来保证数据的一致性和完整性。以 Spring 框架结合数据库事务为例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

@Service
public class OrderService {
@PersistenceContext
private EntityManager entityManager;

@Transactional
public void processOrder(Order order) {
// 保存订单信息到数据库
entityManager.persist(order);
// 假设这里还有更新库存等其他相关操作,都在这个事务内执行
// 如果任何一个操作失败,整个事务会回滚,保证数据的一致性
}
}

上述代码中,@Transactional 注解标记了 processOrder 方法为一个事务方法,当方法内的数据库操作出现异常时,所有已经执行的操作都会回滚,防止出现数据不一致的情况,提高了系统的可靠性。

  • 异常处理机制:在代码中合理地捕获和处理各种可能出现的异常,避免系统因未处理的异常而崩溃。例如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class FileReaderExample {
public static void main(String[] args) {
try {
FileReader fileReader = new FileReader("test.txt");
// 读取文件内容操作
char[] buffer = new char[1024];
int numRead = fileReader.read(buffer);
while (numRead!= -1) {
// 处理读取到的内容
numRead = fileReader.read(buffer);
}
fileReader.close();
} catch (FileNotFoundException e) {
System.out.println("文件未找到,可能需要检查文件路径是否正确");
} catch (IOException e) {
System.out.println("读取文件时出现 IO 错误");
}
}
}

通过 try-catch 块对可能出现的 FileNotFoundException 和 IOException 进行捕获和处理,使得系统在遇到文件相关的异常情况时,依然能够保持一定的稳定性,继续运行或者给出合理的提示信息,增强了系统整体的可靠性。

(三)应用场景

  • 企业资源规划(ERP)系统:涉及大量的企业核心数据和复杂的业务流程,通过可靠的事务管理和全面的异常处理机制,确保在日常操作以及各种突发状况下,数据的准确性和系统的正常运行,避免因数据错误或系统崩溃给企业带来重大损失。
  • 物流管理系统:在货物运输、库存管理等环节,需要处理众多的数据交互和业务操作,依靠可靠的机制保证系统在面对诸如网络波动、设备故障等问题时,依然能准确记录和更新货物状态等信息,保障物流业务的顺利开展。

五、安全性

(一)理解安全性

  • 安全性涵盖了系统对数据的保密性、完整性以及用户身份认证、授权访问等多个方面,防止系统遭受外部攻击(如黑客入侵、数据泄露等)和内部的非法操作。

(二)实现原理

  • 用户认证与授权:
    基于 Spring Security 的用户认证示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("user")
.password("password")
.roles("USER");
}

@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
.and()
.formLogin()
.and()
.httpBasic();
}

@Bean
public PasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
}

上述代码使用 Spring Security 框架配置了简单的用户认证和授权机制,在内存中定义了用户信息,并设置了不同路径的访问权限要求,如 /admin/** 路径需要 ADMIN 角色才能访问,其他请求需要用户认证后才能访问,保障了系统资源的安全访问。

  • 加密算法应用:对敏感数据(如用户密码、银行卡信息等)进行加密存储和传输。例如,使用 java.security.MessageDigest 对密码进行哈希加密:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public class PasswordEncryption {
public static String encryptPassword(String password) {
try {
MessageDigest md = MessageDigest.getInstance("SHA-256");
byte[] hash = md.digest(password.getBytes());
StringBuilder hexString = new StringBuilder();
for (byte b : hash) {
String hex = Integer.toHexString(0xff & b);
if (hex.length() == 1) {
hexString.append('0');
}
hexString.append(hex);
}
return hexString.toString();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
return null;
}
}
}

这样即使数据库数据被窃取,攻击者也很难直接获取到原始的敏感信息,提高了数据的保密性。

  • 防止 SQL 注入与输入验证:
    在处理用户输入时,进行严格的输入验证,防止恶意用户通过构造特殊的输入(如 SQL 注入语句)来攻击系统。例如,在使用 JDBC 操作数据库时:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import java.sql.Connection;
import java.sql.Driver
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Scanner;

public class SQLInjectionPrevention {
public static void main(String[] args) {
try {
Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb", "root", "password");
Scanner scanner = new Scanner(System.in);
System.out.println("请输入用户名:");
String username = scanner.nextLine();
// 使用预编译语句,防止SQL注入
String sql = "SELECT * FROM users WHERE username =?";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
preparedStatement.setString(1, username);
// 执行查询等后续操作
//...
preparedStatement.close();
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}

在上述代码中,通过使用 PreparedStatement 来执行数据库查询操作,它会对用户输入的内容进行预处理,将数据部分当作参数传递,而不是直接拼接在 SQL 语句中,这样就能有效防止恶意用户输入类似 ‘ OR 1=1 – 这样的 SQL 注入语句,保障了数据库操作的安全性。

(三)应用场景

  • 在线银行系统:用户登录时需要严格的身份认证机制,传输和存储用户的账户信息、交易密码等都要进行加密处理,同时在涉及账户查询、转账等操作时,要防止 SQL 注入等安全漏洞,确保用户资金安全和系统稳定运行。
  • 企业办公系统:内部员工登录需要进行身份验证,不同岗位员工有不同的权限访问相应的功能模块和数据资源,并且对于员工上传、下载的数据也要进行安全检测和验证,防止内部数据泄露以及外部攻击。

六、可监控性

(一)理解可监控性

可监控性是指能够实时或者定期地获取系统运行过程中的各种状态信息,如性能指标(CPU 使用率、内存占用、响应时间等)、业务指标(订单数量、活跃用户数等),以便及时发现问题、分析趋势并做出相应的决策。

(二)实现原理

日志记录与分析:在系统关键位置添加日志输出,记录重要的操作、事件以及异常情况等信息。例如,在 Java 中使用 log4j 或 slf4j 等日志框架:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class OrderService {
private static final Logger logger = LoggerFactory.getLogger(OrderService.class);

public void createOrder(Order order) {
try {
// 业务逻辑处理,如验证订单信息、保存订单等操作
logger.info("订单创建成功,订单号: {}", order.getOrderId());
} catch (Exception e) {
logger.error("订单创建过程中出现错误", e);
}
}
}

通过配置日志框架的输出级别、存储位置等,可以将日志信息记录到文件或者发送到日志收集系统中。后续可以使用日志分析工具(如 ELK 日志分析平台,由 Elasticsearch、Logstash 和 Kibana 组成)对海量日志进行搜索、分析,从中发现系统的性能瓶颈、错误原因等情况。

  • 监控指标采集与可视化:利用监控工具(如 Prometheus 结合 Grafana)来采集系统的各项指标并进行可视化展示。以在 Spring Boot 应用中集成 Prometheus 为例,首先添加相关依赖:
1
2
3
4
5
6
7
8
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>

然后在配置文件中进行简单配置开启相关端点:

1
2
3
4
5
6
7
8
9
management:
endpoints:
web:
exposure:
include: '*'
metrics:
export:
prometheus:
enabled: true

这样 Prometheus 就可以采集应用的诸如请求次数、响应时间、内存使用等指标信息,再通过 Grafana 进行配置,将这些指标以直观的图表形式展示出来,方便运维人员和开发人员实时监控系统状态。

(三)应用场景

  • 互联网应用运维:对于像电商平台、视频网站等大型互联网应用,运维团队可以通过可监控性机制实时掌握系统的运行状况,当出现响应时间过长、服务器资源紧张等情况时,能快速定位问题并采取措施,如增加服务器资源、优化代码逻辑等。
  • 软件项目迭代优化:开发团队可以根据监控到的业务指标(如不同功能模块的使用频率、用户操作路径等),了解用户行为和需求,为后续的功能迭代、性能优化提供依据,提升用户体验。

七、容错性

(一)理解容错性

容错性是指系统在部分组件出现故障时,仍能维持一定程度的正常运行,尽可能减少对用户体验和业务功能的影响。

(二)实现原理

缓存机制:使用缓存来减轻后端系统的压力,并且在后端服务暂时不可用或者响应缓慢时,缓存中的数据可以继续提供部分服务。例如,在 Spring 框架中使用 Spring Cache 结合 Redis 缓存:

1
2
3
4
5
6
7
8
9
10
11
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

@Service
public class ProductService {
@Cacheable("products")
public Product getProductById(int id) {
// 实际从数据库或其他数据源获取产品信息的逻辑
return productRepository.findById(id).orElse(null);
}
}

当第一次调用 getProductById 方法获取产品信息后,结果会被缓存到 Redis 中(配置好 Redis 作为缓存存储介质的前提下),下次再查询相同产品 ID 的信息时,直接从缓存中获取,提高了响应速度。即使后端数据库出现短暂故障或者查询压力过大,缓存中的数据依然可以满足部分查询需求,增强了系统的容错性。

  • 降级策略:在系统面临高负载或者部分功能出现故障时,通过降级策略,暂时关闭或简化一些非核心功能,保障核心功能的正常运行。例如,在一个电商平台中,如果推荐系统所在的服务出现故障,可设置降级逻辑,在商品详情页面不展示推荐商品信息,只展示核心的商品描述、价格等内容,保证用户依然可以正常下单购买商品,代码示例如下(以简单的基于配置的降级逻辑为例):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class ProductDetailPage {
private boolean isRecommendationServiceAvailable;

public ProductDetailPage(boolean isRecommendationServiceAvailable) {
this.isRecommendationServiceAvailable = isRecommendationServiceAvailable;
}

public void display() {
System.out.println("商品详情:");
System.out.println("商品名称:[具体商品名]");
System.out.println("商品价格:[具体价格]");
if (isRecommendationServiceAvailable) {
// 调用推荐系统获取推荐商品并展示
displayRecommendations();
} else {
System.out.println("推荐系统暂时不可用,暂不展示推荐商品信息");
}
}

private void displayRecommendations() {
// 实际调用推荐系统的逻辑
}
}

通过这种降级策略,当推荐系统出现问题时,页面依然可以正常展示关键信息,维持了系统的基本功能,提高了容错能力。

(三)应用场景

  • 分布式系统:在由多个微服务组成的分布式系统中,由于网络通信、服务依赖等因素,很容易出现部分服务故障的情况,通过缓存和降级策略等容错机制,能保障整个系统的整体可用性和业务连续性。
  • 云计算平台服务:面对大量用户的不同需求和各种复杂的运行环境,云计算平台需要具备良好的容错性,当某个计算节点、存储节点出现故障时,利用缓存数据、启动备用资源或者降级服务等方式,确保用户使用的核心功能不受太大影响,维持平台的稳定运行。

八、总结

作为架构师,深刻理解并妥善应用系统的这些非功能性需求是构建高质量、可靠且能满足用户期望的系统的关键所在。性能要求关乎系统的运行效率,可扩展性决定了系统能否适应业务的发展,可用性、可靠性保障了系统稳定持久地服务,安全性保护系统免受各种威胁,可监控性助力我们及时洞察系统状态,而容错性则让系统在面对意外情况时依然能够坚强 “屹立”。通过合理的架构设计、代码实现以及技术选型,将这些非功能性需求融入到系统的每一个环节中,才能打造出真正优秀的软件系统,满足日益复杂和多样化的业务场景需求。

无套路、关注即可领。持续更新中
关注公众号:搜 架构研究站,回复:资料领取,即可获取全部面试题以及1000+份学习资料
在这里插入图片描述