行业案例:有赞100Wqps透明多级缓存解决方案(TMC)
假设10W人同时访问,如何保证不雪崩?
如果面试遇到这个问题,很多小伙伴的第一反应可能是:
“怎么可能?我们的系统,总体的用户量不到1万,怎么可能会有10W人同时访问?”
这个问题,假如真的遇到了,确实很难应对。但如果直接反问面试官,“怎么可能会有10W人同时访问?”面试官肯定会认为你没有遇到过类似的情况。
为什么呢?
因为哪怕用户不到1万,还是有可能会有10W人同时访问。这些人中可能包括:爬虫刷子(羊毛党)等。
爬虫对访问量的贡献
大家可能听过一句话:整个互联网上,大约有50%以上的流量其实是爬虫。
有个做反爬虫的朋友发现了一个极端案例——在某个页面的12000次点击中,98% 的点击率是爬虫贡献的。
爬虫和用户的比例为 19:1。
那么,假设是 1W 用户,就可能会对应到 19W 爬虫。
那么,1W 用户,有没有10W的同时访问的可能呢?
由于大量爬虫的存在,当然是有的。
刷子用户(羊毛党)对访问量的贡献
“羊毛党”战术之一:开启机器人批量注册新账号,招募“新兵”。
这种战术是一个“昏招”,批量注册的账号很容易被识别出来。
“羊毛党”战术之二:提前囤积账号,囤积的老账号。
但是通过注册时间、注册地点与下单地点比对等方式,很快就能识别出来。
那么,1W 用户,可能会对应到多少羊毛党用户呢?
其中,可能会包含部分提前囤积的账号。
另外,在活动执行的过程中,也还是有可能批量注册大量的新账号。
那么,1W 用户,是否有可能对应到 10W 的同时访问呢?
只要有利可图,就会有刷子用户(羊毛党)。他们会通过群体人手或者自动化工具,制造大量的瞬间流量。
这些自动化工具,可能在 1秒 内尝试 10W 次。
所以,只要有利可图,比如秒杀等活动,1W 用户,是否有可能会有 10W 的同时访问?
当然有可能!
最近无意间获得一份阿里大佬写的刷题笔记,一下子打通了我的任督二脉,进大厂原来没那么难。
这是大佬写的 7701页的BAT大佬写的刷题笔记,让我offer拿到手软
假设10W人突然访问,现有架构能否扛住?
按照之前的架构理论,一个系统10W人同时访问,也就是并发量为 10W QPS。
那么,10W QPS,对应到多少用户量呢?
是一个1亿的用户量。
而我们很多同学手上的系统,总体的用户量不到1万。
不到1万的用户,对应到多少的吞吐量呢?
是10。
没错,如果总体的用户量不到1万,按照正常估算,吞吐量就只有 10。
也就是说,如果我们按照1万来设计系统架构,系统能够承受的最大吞吐量是 10。
那么,如果发生突发情况,假设10W人突然访问,我们的架构,如何抵抗?
大家看看,上面的架构,能抵抗得住吗?
接入层和服务层如何抵抗?
方式之一:扩容
当面对大量并发访问时,大家通常会想到的第一个策略就是扩容。
但是,如果流量是突发的,且我们无法预测流量高峰的时段,如何应对呢?
解决方案是自动扩容。
自动扩容虽然较难实现,但只要稍微想一下,方法总是有的。通常有两种方式可以实现自动扩容:
其次能用到的策略:限流
Nginx限流 和 SpringCloud Gateway限流 是常见的限流策略。
在接入层,我们可以使用Nginx限流,而在微服务层,SpringCloud Gateway 里则可以使用 Redis Lua 进行分布式限流,或者使用 Sentinel 进行限流。
通过扩容和限流的结合,我们的系统应该能够承受10W QPS的流量,因为可以将流量限制在1W QPS,甚至是1K QPS。
然而,如果有大量刷子流量或爬虫流量,这种限流方式并不理想。如果10W QPS都是有效流量,限流可能就不适用了,这时流量必须进入服务层。
分布式 Redis 集群如何抵抗?
当流量进入服务层后,接下来的流量将会进入到 Redis 集群。
一般来说,Redis 集群会搭建成 3主3从 的配置。
在这个配置中,主节点 提供服务,而 从节点 做冗余,并不提供数据写入服务。
Redis Cluster 模式下,官方默认主节点提供读写,而从节点只提供 slot 数据备份和故障转移。默认情况下,从节点并不提供数据的读写服务。
单个 Redis 节点的吞吐量大约是 2W QPS,但是如果是 10W QPS 的流量访问 Redis 集群,分布到 3 个节点,仍然不够。特别是当 10W QPS 都访问同一个 key 时,问题更加严重。
由于单个 Redis 节点的吞吐量限制在 2W QPS,那么 如何解决这种瓶颈呢?
解决方案
有两种方案可以应对 Redis 的瓶颈:
-
Redis 扩容
Redis 扩容可以解决某些场景中的高并发问题,特别是当访问量相对均匀时。比如,如果扩容到 10主10从,就可以承受 20W QPS 的流量。但如果 20W QPS 的流量都来自于一个 key,这种方案就不适用了。 -
本地缓存
本地缓存可以解决 hotkey(热key) 的问题,通常单个 key 会占据大量流量。如果某个 key 的访问量非常大,可以将其缓存到本地缓存系统中,减轻 Redis 的压力。
通过引入本地缓存,可以有效分担 Redis 集群的压力,避免因为单个热 key 的过度访问导致系统崩溃。
总结
- 扩容和限流可以有效应对接入层的流量压力;
- Redis 扩容和本地缓存是应对 Redis 集群瓶颈的有效手段,尤其是针对流量集中在单一 key 的场景。
通过合理的架构设计,系统能够高效应对突发的流量高峰。
解决 10W QPS 突发流量的本地缓存架构
为了解决 10W QPS 突发流量的问题,本地缓存架构有两种常见的方式:
- 二级缓存架构
- 三级缓存架构
二级缓存架构
二级缓存架构通常包括Java 本地缓存(如 Caffeine)和Redis 分布式缓存。具体架构如下图所示:
工作原理:
- 一级缓存:使用 Java 本地缓存(例如 Caffeine)。如果缓存中有数据,就直接返回。
- 二级缓存:如果一级缓存没有找到,则访问 Redis 进行数据获取。
三级缓存架构
三级缓存架构在二级缓存的基础上,增加了Nginx本地缓存,进一步提高性能。具体架构如下图所示:
工作原理:
- Nginx 本地缓存:首先检查缓存中的数据。
- Java 本地缓存:如果 Nginx 缓存没有,进一步检查 Java 本地缓存。
- Redis 分布式缓存:如果以上都没有,再通过 Redis 进行数据获取。
最近无意间获得一份阿里大佬写的刷题笔记,一下子打通了我的任督二脉,进大厂原来没那么难。
这是大佬写的 7701页的BAT大佬写的刷题笔记,让我offer拿到手软
本地缓存的优缺点
1. 快但是量少:访问速度快,但无法进行大数据存储
本地缓存的优点是性能优越,因为数据不需要跨网络传输。但由于其占用应用进程的内存空间(例如 Java JVM 内存),因此不能进行大量数据存储。
2. 需要解决数据一致性问题
本地缓存只支持该应用进程访问,因此如果应用部署在多个节点上,且数据库数据发生变化,就需要同步更新各个节点的缓存数据。为确保数据一致性,通常使用发布订阅机制(如基于 RocketMQ)来同步缓存更新,这会增加系统的复杂度。
3. 未持久化,容易丢失
由于本地缓存是存储在应用进程的内存中,因此在进程重启时缓存数据会丢失。因此需要特别关注缓存数据的持久化,尤其是需要保存并更改的数据。
4. 需要尽量缓存热点key,提升缓存命中率
本地缓存的空间较小,容易被淘汰。因此,提高缓存的命中率变得尤为重要。
如何提升缓存的命中率?
方式1:采用更好的缓存淘汰策略
例如,Caffeine 使用了 W-TinyLFU 策略,相比传统的 LFU 和 LRU 策略,W-TinyLFU 提供更高的命中率。测试表明,Caffeine 在不同场景下的命中率比 Guava 高出 10% 以上。
方式2:尽量识别并缓存热点数据
将访问频繁的热点数据缓存到本地缓存中,增加缓存的命中率。
什么是 HotKey?
当某个 Key 接收到的访问次数显著高于其他 Key 时,我们将其称为 HotKey。常见的 HotKey 有:
- Redis 实例的访问量集中在某个 Key 上:例如,一个 Redis 实例每秒的总访问量为 10000,而其中一个 Key 每秒的访问量达到了 7000。
- 数据库中的热数据:例如,频繁访问的商品信息(如 SKU、店铺 ID 等)。
- 机器人、爬虫、刷子用户:如用户的 userId、uuid、ip 等。
- 高频接口请求:如某个商品的查询接口
/sku/query
等。
HotKey 的风险
在高并发环境中,HotKey 会导致严重问题:
- 突发流量导致服务瘫痪:例如,某个商品成为爆款时,可能会引发数百万级别的请求,这会迅速占满 Redis 分片集群,导致系统无法处理其他请求。
- Redis 队列堵塞:Redis 是单线程结构,过高的请求量可能导致请求排队,最终导致系统超时。
- Redis 击穿:当 Redis 无法响应时,流量将直接转到数据库,导致数据库雪崩。
如何解决 HotKey 问题?
- 热缓存分流:通过将热点数据分流到多个 Redis 实例或分片,减少单个节点的压力。
- 限流:对于突发的 HotKey 请求,可以采用限流机制,控制访问频率。
- 本地缓存策略:使用本地缓存来缓解 Redis 集群的压力,避免所有请求都直接访问 Redis。
总结一下,HotKey 带来的常见问题
-
占用大量的 Redis CPU 时间
HotKey 会导致 Redis 占用过多的 CPU 时间,从而使 Redis 性能下降,并影响其他正常请求的处理。 -
Redis Cluster 中各节点流量不均衡
在 Redis 集群中,由于某些节点的负载过高而其他节点相对空闲,导致 Redis 集群的分布式优势无法被充分利用,产生读/写热点问题。 -
在抢购、秒杀活动中的问题
在这种活动中,商品的库存对应的 Key 的请求量过大,超出了 Redis 的处理能力,从而可能导致 超卖 问题。 -
HotKey 请求压力超出 Redis 的承受能力
当 HotKey 的请求数量超出 Redis 的承载范围时,导致缓存击穿,进而大规模的请求直接访问后端存储,导致后端数据库崩溃,进而影响到其他业务。
HotKey 问题的根本原因
HotKey 问题的核心:如何识别 HotKey,并将其存储到本地内存。
- 原因:只要 Key 在本地内存中,我们就能极快地对其进行处理,内存访问的速度远远超过 Redis 访问速度。
- 内存优势:当 HotKey 存储在内存时,每秒能够处理数万次请求,且不存在数据层的瓶颈。
- 问题:但是问题是,事先不知道哪个 Key 会变成 HotKey。
因此,核心问题在于 如何进行 HotKey 的探测?
HotKey 探测的关键指标
1. 实时性
HotKey 的产生通常是突发性的,并且变化非常迅速。某个活动或商品的热度突然爆发时,HotKey 很快就会出现。如果无法及时识别并将其加载到内存中,可能导致 Redis 集群被压垮。因此,探测系统需要具备实时性,在 Key 热度刚开始上升时(1秒内)就能够将其加载到服务集群的内存中,避免 Redis 集群超负荷。
2. 准确性
准确性是非常重要的。HotKey 探测需要通过累加请求数量并设定阈值,保证探测到的 HotKey 是精准的,避免误探或者漏探。
3. 集群一致性
在一些带有删除 Key 的场景中,需要确保当某个 Key 被删除时,整个集群中的该 Key 也能够同步删除,避免数据不一致的情况。
4. 高性能
高性能是 HotKey 探测的核心之一。实现高性能可以降低数据层的负载,提升应用层的性能,并节省服务器资源。如果系统能够以较低的资源消耗完成实时的 HotKey 探测,经济价值将大大提升。
最近无意间获得一份阿里大佬写的刷题笔记,一下子打通了我的任督二脉,进大厂原来没那么难。
这是大佬写的 7701页的BAT大佬写的刷题笔记,让我offer拿到手软
如何实现 HotKey 探测?
HotKey 探测方案 1: 流计算集群
通过流式计算集群(如 Storm 或 Flink)进行 HotKey 的探测。具体流程如下:
- 应用:将访问记录发送到消息队列(如 Kafka)。
- 流计算:使用 Storm 或 Flink 集群进行 Top N 的计算,找到访问量最大的 Key。
- Redis 存储:将计算得到的 Top N Key 存入 Redis,作为潜在的 HotKey。
HotKey 探测方案 2: 有赞透明多级缓存解决方案
有赞提出的 透明多级缓存解决方案(TMC) 可以有效解决 HotKey 的探测和缓存问题。这种方案通过多级缓存机制,可以更加高效地处理突发流量,并减少缓存失效带来的风险。
HotKey 探测方案 3: 结合开源 HotKey 进行热点探测
例如,结合 京东开源的 HotKey 探测框架,来进行热点数据的探测。这些开源方案通常已经经过大规模的测试和优化,可以直接应用到生产环境中,帮助快速识别和应对 HotKey 问题。
TMC 简介
1-1. TMC 是什么
TMC(透明多级缓存,Transparent Multilevel Cache)是由有赞 PaaS 团队为公司内应用提供的整体缓存解决方案。
TMC 在通用的分布式缓存解决方案(如 CodisProxy + Redis,或者有赞自研的分布式缓存系统 zanKV)基础上,增加了以下功能:
- 应用层热点探测
- 应用层本地缓存
- 应用层缓存命中统计
这些功能帮助应用层解决缓存使用过程中出现的热点访问问题。
1-2. 为什么要做 TMC
使用有赞服务的电商商家数量庞大,商家会不定期进行各种“商品秒杀”或“商品推广”活动,导致“营销活动”、“商品详情”、“交易下单”等链路应用产生缓存热点访问问题。由于活动类型和商品信息难以预测,热点访问不可提前预见。
在热点访问期间,某些热访问的 Key 会产生大量缓存请求,冲击分布式缓存系统,最终影响应用层系统的稳定性。因此,TMC 旨在提供一个能够 自动发现热点 并将热点缓存访问请求前置到应用层本地缓存的解决方案。
1-3. 多级缓存解决方案的痛点
TMC 设计时,解决了以下多级缓存方案的需求痛点:
- 热点探测:如何快速且准确地发现热点访问 Key?
- 数据一致性:如何保障应用层本地缓存与分布式缓存系统的数据一致性?
- 效果验证:如何让应用层查看本地缓存的命中率和热点 Key 等数据,以验证多级缓存的效果?
- 透明接入:如何减少对应用系统的入侵,实现平滑接入?
TMC 通过聚焦这些痛点,设计并实现了整体解决方案,支持“热点探测”和“本地缓存”,减少热点访问时对下游分布式缓存服务的冲击,避免影响应用服务的性能及稳定性。
2. TMC 整体架构
TMC 的整体架构如图所示,共分为三层:
- 存储层:提供基础的 KV 数据存储能力,针对不同的业务场景选用不同的存储服务(如 Codis、zanKV、Aerospike)。
- 代理层:为应用层提供统一的缓存使用入口及通信协议,承担分布式数据水平切分后的路由功能转发。
- 应用层:提供统一客户端给应用服务使用,内置“热点探测”和“本地缓存”等功能,对业务透明。
3. TMC 本地缓存
3-1. 如何透明接入
TMC 如何减少对业务应用系统的入侵,做到透明接入呢?
对于公司 Java 应用服务,TMC 主要有两种缓存客户端方式:
- 基于 spring.data.redis 包,使用 RedisTemplate 编写业务代码;
- 基于 youzan.framework.redis 包,使用 RedisClient 编写业务代码。
不论采用哪种方式,最终通过 JedisPool 创建的 Jedis 对象与缓存服务端代理层做请求交互。
TMC 对原生 Jedis 包的 JedisPool 和 Jedis 类进行了改造。在 JedisPool 初始化时,集成了 TMC 的“热点发现 + 本地缓存”功能,使得 Jedis 客户端与缓存服务端代理层交互时,先与 Hermes-SDK 交互,从而完成透明接入。
通过这种方式,Java 应用服务只需要使用特定版本的 Jedis-jar 包,无需修改现有业务代码,即可接入 TMC 使用“热点发现 + 本地缓存”功能,从而达到了对应用系统的最小入侵。
3-2. 整体结构
TMC 本地缓存的整体结构分为以下几个模块:
- Jedis-Client:Java 应用与缓存服务端交互的直接入口,接口定义与原生 Jedis-Client 无异。
- Hermes-SDK:自研的“热点发现 + 本地缓存”功能的 SDK 封装,Jedis-Client 通过与 Hermes-SDK 交互来集成这些能力。
- Hermes 服务端集群:接收 Hermes-SDK 上报的缓存访问数据,进行热点探测,将热点 Key 推送给 Hermes-SDK 进行本地缓存。
- 缓存集群:由代理层和存储层组成,为应用客户端提供统一的分布式缓存服务入口。
- 基础组件:如 etcd 集群、Apollo 配置中心,为 TMC 提供“集群推送”和“统一配置”能力。
3-2-1. 基本流程
-
Key 值获取:
当 Java 应用调用 Jedis-Client 接口获取缓存时,Jedis-Client 会询问 Hermes-SDK 当前 Key 是否是热点 Key。- 如果是热点 Key,直接从 Hermes-SDK 获取本地缓存的值,而不去访问缓存集群。
- 如果不是热点 Key,Hermes-SDK 会通过回调接口从缓存集群获取值。
-
Key 值过期:
当调用set()
、del()
、expire()
等接口使得 Key 过期时,Jedis-Client 会同步调用 Hermes-SDK 的invalid()
方法,通知其 Key 值失效。
对于热点 Key,Hermes-SDK 会先在本地缓存中将 Key 失效,并通过通信模块推送到集群内的其他节点,保证数据一致性。 -
热点发现:
Hermes 服务端集群会定期(每 3 秒)收集 Hermes-SDK 上报的 Key 访问事件,并分析缓存访问数据,进行热点探测。探测到的热点 Key 会通过 etcd 集群推送给应用集群的 Hermes-SDK 通信模块,通知它们将热点 Key 加入本地缓存。 -
配置读取:
Hermes-SDK 和 Hermes 服务端集群会在启动和运行时,从 Apollo 配置中心读取配置信息(如启动关闭配置、热点阈值、etcd 地址等)。
3-2-2. 稳定性
TMC 本地缓存的稳定性体现在以下几个方面:
- 数据上报异步化:Hermes-SDK 使用 rsyslog 技术对 Key 访问事件进行异步上报,避免阻塞业务。
- 通信模块线程隔离:Hermes-SDK 的通信模块使用独立的线程池和有界队列,保证事件上报和业务执行线程的隔离。
- 缓存管控:Hermes-SDK 的热点模块控制本地缓存的大小上限,确保其占用内存不超过 64MB,避免 JVM 堆内存溢出。
3-2-3. 一致性
TMC 本地缓存的一致性体现在:
- 本地强一致性:热点 Key 发生变更时,Hermes-SDK 会同步失效本地缓存。
- 集群最终一致性:热点 Key 变更导致的缓存失效事件通过 etcd 集群广播,确保集群中所有节点的本地缓存最终一致。
四、TMC 热点发现
4-1. 整体流程
TMC 的热点发现流程包括四个步骤:
- 数据收集:收集 Hermes-SDK 上报的 Key 访问事件。
- 热度滑窗:对每个 Key 维护一个时间轮,记录基于当前时刻滑窗的访问热度。
- 热度汇聚:对所有 Key 进行热度汇总,得到排序结果。
- 热点探测:从热度汇总结果中选出 TopN 的热点 Key,并推送给 Hermes-SDK。
4-2. 数据收集
Hermes-SDK 通过本地 rsyslog 将 Key 访问事件以协议格式放入 Kafka,Hermes 服务端集群的每个节点消费 Kafka 消息,实时获取 Key 访问事件。
访问事件协议格式如下:
- appName:集群节点所属业务应用
- uniqueKey:业务应用 Key 的访问事件
- sendTime:访问事件发生时间
- weight:访问权值
Hermes 服务端集群节点将收集到的 Key 访问事件存储在本地内存中,数据结构为:
Map<String, Map<String, LongAdder>>,即:
Map<appName, Map<uniqueKey, 热度>>
4-3. 热度滑窗
4-3-1. 时间滑窗
Hermes 服务端集群节点维护每个 App 的每个 Key 的时间轮。
时间轮中有 10个时间片,每个时间片记录当前 Key 对应 3 秒周期内的总访问次数。
通过累加 10 个时间片的记录,可以表示当前 Key 从当前时间向前 30 秒的总访问次数。
4-3-2. 映射任务
每 3 秒,Hermes 服务端集群节点生成映射任务,由节点内的“缓存映射线程池”执行。
映射任务的内容如下:
- 取出当前 App 的 Key 和热度数据。
- 遍历 Key,将每个 Key 的热度存入其对应时间轮的时间片中。
4-4. 热度汇聚
完成热度滑窗后,映射任务继续对当前 App 进行热度汇聚。
遍历 App 的所有 Key,将每个 Key 的时间轮热度汇总得到总热度,并将 <Key, 总热度> 存入 Redis 存储服务,形成热度汇聚结果。
4-5. 热点探测
在每 3 秒执行一次的映射任务中,每个 App 会生成当前时刻的热度汇聚结果。
Hermes 服务端集群中的热点探测节点周期性地从最近的热度汇聚结果中提取达到热度阈值的 TopN Key 列表,并得到本次探测的热点 Key。
TMC 热点发现整体流程如下图所示:
4-6. 特性总结
4-6-1. 实时性
Hermes-SDK 基于 rsyslog + Kafka 实时上报 Key 访问事件。
每 3 秒,映射任务完成“热度滑窗”+“热度汇聚”工作,若有热点访问场景出现,最多 3 秒即可探测到热点 Key。
4-6-2. 准确性
Key 的热度汇聚结果通过“基于时间轮实现的滑动窗口”准确反映了当前及最近的访问分布。
4-6-3. 扩展性
Hermes 服务端集群节点无状态,可以根据 Kafka 的 partition 数量横向扩展。
热度滑窗和热度汇聚过程基于 App 数量,在单节点内可多线程扩展。
五、TMC 实战效果
5-1. 快手商家某次商品营销活动
有赞商家通过快手直播平台进行商品营销活动,导致该商品短时间内成为热点,活动期间 TMC 记录的实际热点访问效果如下:
5-1-1. 某核心应用的缓存请求 & 命中率曲线图
上图绿线表示获取缓存操作命中的次数,反映了 TMC 本地缓存命中的情况。
5-1-2. 热点缓存对应用访问的加速效果
通过 TMC 本地缓存,应用接口的请求量大幅增长,但响应时间 (RT) 反而有所下降。
5-1-3. 热点缓存加速效果
活动期间应用接口的请求量明显增长,但 TMC 本地缓存带来的加速效果显著降低了接口的响应时间。
5-2. 双十一期间部分应用 TMC 效果展示
5-2-1. 商品域核心应用效果
5-2-2. 活动域核心应用效果
六、TMC 功能展望
TMC 目前已经为多个核心应用模块(如商品中心、物流中心、库存中心、营销活动、用户中心、网关 & 消息等)提供服务,后续更多应用正在陆续接入。
TMC 提供了灵活的配置选项,应用服务可以根据实际业务需求在“热点阈值”、“热点 Key 探测数量”、“热点黑白名单”维度进行自定义配置,从而获得更好的使用效果。
HotKey 探测方案 3:结合开源 HotKey 进行热点探测
结合开源的 HotKey 进行热点探测,有助于缓存系统的重构。下面对比了重构前后的不同特点:
特性 | 重构系统前 | 使用京东 HotKey 重构系统后 |
---|---|---|
机器资源 | 高配物理机/虚拟机 | 普通物理机/虚拟机/容器 |
管控复杂性 | 无法控制热点,不易监控 | 热点数据可以监控,支持手动刷新 |
资源利用率 | 资源利用率低 | 资源利用率高,大部分热点数据持有资源 |
突发流量 | 无法弹性应对突发流量 | 弹性应对突发流量 |
预发流量 | 预设所有数据 | 只提前预设热点数据 |
数据一致性 | 集群内数据不一致 | 集群内数据一致性高 |
假设 10W 人同时访问,如何保证不雪崩?
解决方案:扩容、限流和三级缓存相结合,尤其是 热点探测,可以显著提升 本地缓存命中率。此外,利用 刷子用户识别 和 Bloom 过滤器,可以提高系统的安全性。
最后说一句(求关注,求赞,别白嫖我)
最近无意间获得一份阿里大佬写的刷题笔记,一下子打通了我的任督二脉,进大厂原来没那么难。
这是大佬写的 7701页的BAT大佬写的刷题笔记,让我offer拿到手软
本文,已收录于,我的技术网站 cxykk.com:程序员编程资料站,有大厂完整面经,工作技术,架构师成长之路,等经验分享
求一键三连:点赞、分享、收藏
点赞对我真的非常重要!在线求赞,加个关注我会非常感激!
真的免费,如果你近期准备面试跳槽,建议在cxykk.com在线刷题,涵盖 1万+ 道 Java 面试题,几乎覆盖了所有主流技术面试题、简历模板、算法刷题