尼恩说在前面
什么是热点key? 热点key会带来哪些问题? 如何监测热点key?如何解决热点key? 你们生产遇到过 hotkey吗? 怎么解决的?
最新《尼恩 架构笔记》《尼恩高并发三部曲》《尼恩Java面试宝典》的PDF,请关注本公众号【技术自由圈】获取,回复:领电子书
本文目录
- 尼恩说在前面:
- 1 宏观总结:热点key的危害和解决方案
- 什么是HotKey?
- 2 热点Key的巨大危害
- 2.1 HotKey对服务层和数据层的风险
- 2.2 HotKey带来的常见问题
- 2.3 热点Key的巨大危害
- 3 HotKey的解决方案
- 3.1 本地缓存方案: 实现就近访问
- 3.2 打撒热点:将热点Key分散到不同的服务器
- 3.3 Redis 从节点扩容
- 3.4 热点探测
- 3.5 本地缓存的预加载
- 4 HotKey的探测方案
- 4.1 代理端探测:
- 4.2 服务端探测:
- 4.3 客户端探测:
- 4.4 凭经验判断:
- 5 服务端探测 HotKey:
- 5.1 通过 MONITOR命令在服务端探测 HotKey:
- 5.1.1 MONITOR命令语法
- 5.1.2 使用`MONITOR`命令监控hotkey
- 5.1.3 通过 redis-faina 工具实现 MONITOR 的分析与定位
-安装 `redis-faina`
- 5.1.4 通过管道从 stdin 读取 N 条命令进行分析处理:
- 5.1.5 直接从文件中读取 N 条命令进行分析处理:
- 5.1.6 MONITOR 的消耗
- MONITOR 不记录的命令
- 5.2 通过 Redis的 hotkey 分析工具在服务端探测 HotKey:
- 5.2.1 redis-cli`提供了`--hotkeys的用法
- 5.2.2 redis-cli --hotkeys 的缺点
- 6 代理端探测探测 HotKey:
- 7 在redis client 客户端探测 HotKey
- 7.1:大厂方案1: 透明多级缓存 (TMC) 架构设计
- 7.1.1 透明多级缓存 (TMC)简介
-有赞 为什么要做 TMC
-多级缓存解决方案的痛点
- 7.1.2 透明多级缓存 (TMC)整体架构
- 7.1.3 透明多级缓存 (TMC)本地SDK缓存
-如何实现透明 ?
- 7.1.4 透明多级缓存 (TMC)本地SDK缓存 整体结构
- 7.1.5 透明多级缓存 (TMC)本地SDK 模块划分
- 7.1.6 透明多级缓存 (TMC)本地SDK 基本流程
- 7.1.7 透明多级缓存 (TMC)本地SDK 稳定性
- 7.1.8 透明多级缓存 (TMC)本地SDK 一致性
- 7.1.9 TMC热点探测流程
-TMC热点探测 整体流程
- 7.1.9.1 数据收集
- 7.1.9.2 热度滑窗
-1: 时间滑窗
-2. 映射任务
- 7.1.9.3 热度汇聚
- 7.1.9.4 热点探测
- 7.1.10 TMC热点探测 特性总结
-1. 实时性
-2. 准确性
-3.扩展性
- 7.1.11 TMC实战效果
- 7.1.11.1 快手商家某次商品营销活动
- 7.1.11.2 热点缓存对应用访问的加速效果
- 7.1.11.3 双十一期间部分应用 TMC 效果展示
-1. 商品域核心应用效果
-5-2-2. 活动域核心应用效果
- 7.1.12 TMC功能的总结
- 7.2 大厂方案2: 结合开源hotkey,做热点探测和预加载
- 说在最后:有问题找老架构取经
1 宏观总结:热点key的危害和解决方案
什么是HotKey?
某Redis实例的每秒总访问量为10000,而其中一个Key的每秒访问量达到了7000(访问次数显著高于其它Key) 对一个拥有上千个成员且总大小为1MB的HASH Key每秒发送大量的HGETALL(带宽占用显著高于其它Key) 对一个拥有数万个成员的ZSET Key每秒发送大量的ZRANGE(CPU时间占用显著高于其它Key)
2 热点Key的巨大危害
2.1 HotKey对服务层和数据层的风险
比如秒杀活动、热点微博、热评,某件商品被数万次点击浏览或购买时,就会造成热点问题 比如大量发布、浏览的热点新闻、热点评论等读多写少场景也会产生热点问题 比如是 瞬间大量开启的爬虫用户, 突发大批机器人以远超正常用户的速度发起极其密集的请求,这些机器人只需要很小的代价,就能发出百倍于普通用户的请求量,从而大幅挤占正常用户的资源。
2.2 HotKey带来的常见问题
HotKey占用大量的Redis CPU时间,使其性能变差并影响其它请求; Redis Cluster中各node流量不均衡造成Redis Cluster的分布式优势无法被Client利用,一个分片负载很高而其它分片十分空闲从而产生读/写热点问题; 在抢购、秒杀活动中,由于商品对应库存Key的请求量过大,超出Redis处理能力造成超卖; HotKey的请求压力数量超出Redis的承受能力造成缓存击穿,此时大量强求将直接指向后端存储,将后端存储打挂并影响到其它业务;
流量过于集中,突破物理网卡的极限 请求过多,缓存分片服务被打垮 穿透DB
2.3 热点Key的巨大危害
3 HotKey的解决方案
3.1 本地缓存方案: 实现就近访问
3.2 打撒热点:将热点Key分散到不同的服务器
3.3 Redis 从节点扩容
3.4 热点探测
3.5 本地缓存的预加载
4 HotKey的探测方案
4.1 代理端探测:
4.2 服务端探测:
4.3 客户端探测:
4.4 凭经验判断:
5 服务端探测 HotKey:
redis-cli --hotkeys
命令:redis-cli
提供了--hotkeys
选项,可以帮助分析哪些Key是热点Key。5.1 通过 MONITOR命令在服务端探测 HotKey:
MONITOR
命令的开销较大,所以,MONITOR命令 一般只在调试阶段使用。MONITOR
命令监控hotkey,首先需要了解MONITOR
命令的基本用途和使用方法。5.1.1 MONITOR命令语法
redis 127.0.0.1:6379> MONITOR
redis-cli
和 telnet
调用MONITOR 。$ redis-cli monitor
1339518083.107412 [0 127.0.0.1:60866] "keys" "*"
1339518087.877697 [0 127.0.0.1:60866] "dbsize"
1339518090.420270 [0 127.0.0.1:60866] "set" "x" "6"
1339518096.506257 [0 127.0.0.1:60866] "get" "x"
1339518099.363765 [0 127.0.0.1:60866] "del" "x"
1339518100.544926 [0 127.0.0.1:60866] "get" "x"
redis-cli
运行 MONITOR 时,可以发送 SIGINT
(Ctrl+C) 信号来停止退出。$ telnet localhost 6379
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
MONITOR
+OK
+1339518083.107412 [0 127.0.0.1:60866] "keys" "*"
+1339518087.877697 [0 127.0.0.1:60866] "dbsize"
+1339518090.420270 [0 127.0.0.1:60866] "set" "x" "6"
+1339518096.506257 [0 127.0.0.1:60866] "get" "x"
+1339518099.363765 [0 127.0.0.1:60866] "del" "x"
+1339518100.544926 [0 127.0.0.1:60866] "get" "x"
QUIT
+OK
Connection closed by foreign host.
telnet
运行MONITOR时,可以发送 QUIT
来停止退出。5.1.2 使用MONITOR命令监控hotkey
MONITOR
命令主要用于实时打印出Redis服务器接收到的命令,这包括所有对Redis数据库的操作,如读取和写入操作。MONITOR
命令监控hotkey的步骤:启动Redis客户端:首先,你需要连接到Redis服务器。这可以通过 redis-cli
命令行工具完成。执行MONITOR命令:在Redis客户端中输入 MONITOR
命令。这将启动监控模式,实时输出所有接收到的命令。例如:redis 127.0.0.1:6379> MONITOR OK 1410855382.370791 [0 127.0.0.1:60581] "info" 1410855404.062722 [0 127.0.0.1:60581] "get" "a"
需要注意的是,`MONITOR`命令虽然非常有用,但它会对Redis服务器的性能产生一定影响。
顺便说一下: 和monitor命令一起,Redis 提供了一组内置的命令来获取 服务器 的状态信息,这些命令可以用来进行基本的监控。
INFO all
INFO clients
或 INFO memory
。MONITOR:实时显示所有到达 Redis 服务器的命令。这在调试时非常有用,但在生产环境中应谨慎使用,因为它会显著影响性能。 MONITOR SLOWLOG:记录执行时间超过配置阈值的命令。这对于识别慢查询非常有用。 SLOWLOG GET
5.1.3 通过 redis-faina 工具实现 MONITOR 的分析与定位
MONITOR
命令输出,帮助用户对 Redis 实例进行性能诊断。redis-faina
是由 Instagram 开发并开源的一个 Redis 查询分析工具。安装 redis-faina
redis-faina
:git clone https://github.com/facebookarchive/redis-faina.git
redis-faina
的使用也非常简单,你可以通过以下两种方式之一来使用它:5.1.4 通过管道从 stdin 读取 N 条命令进行分析处理:
redis-cli -p 6379 MONITOR | head -n | redis-faina.py [options]
redis-cli
命令连接到 Redis 服务器,并执行 MONITOR
命令。head -n
用于限制分析的命令数量,而 redis-faina.py
是分析工具的 Python 脚本。redis-cli -p 6379 monitor | head -n 1000 | redis-faina.py
Overall Stats
========================================
Lines Processed 7904
Commands/Sec 256.80
Top Prefixes
========================================
n/a
Top Keys
========================================
970125 3952 (50.00%)
aaa850 2 (0.03%)
aaa919 2 (0.03%)
aaa852 2 (0.03%)
aaa853 2 (0.03%)
aaa678 2 (0.03%)
aaa679 2 (0.03%)
aaa856 2 (0.03%)
Top Commands
========================================
AUTH 3952 (50.00%)
set 3950 (49.97%)
Command Time (microsecs)
========================================
Median 1920.0
75% 2016.25
90% 2092.0
99% 2840.0
Heaviest Commands (microsecs)
========================================
AUTH 22542297.5
set 8236623.75
Slowest Calls
========================================
20718202.0 "AUTH" "970125"
8456.0 "set" "aaa82" "aaaaaaaa82"
6624.0 "set" "aaa103" "aaaaaaaa103"
6506.0 "set" "aaa817" "aaaaaaaa817"
6105.0 "set" "aaa2024" "aaaaaaaa2024"
6081.0 "set" "aaa1057" "aaaaaaaa1057"
6074.75 "set" "aaa1948" "aaaaaaaa1948"
6067.0 "set" "aaa576" "aaaaaaaa576"
5.1.5 直接从文件中读取 N 条命令进行分析处理:
redis-cli -h xx.xx.xx.xx -p 6379 -a pwd monitor | head -n > /tmp/outfile.txt
redis-faina.py /tmp/outfile.txt
MONITOR
命令的输出重定向到一个文件中,然后使用 redis-faina.py
脚本分析该文件。redis-faina
会输出包括总命令数、每秒命令数(QPS)、最频繁使用的前缀、最频繁使用的键、最频繁执行的命令、命令执行时间的统计分布、最耗时的命令以及最慢的调用等统计信息。这些信息对于分析 Redis 性能和识别热点键(hotkey)非常有用。redis MONITOR
输出的只有请求开始的时间,所以在一个非常繁忙的 Redis 实例中,根据该请求的开始时间以及下一个请求的开始时间,可以大概估算出一个请求的执行时间。redis-faina
统计的时间并不是十分精确的,尤其在分析一个非常闲的 Redis 实例时,分析的结果可能差异较大5.1.6 MONITOR 的消耗
$ src/redis-benchmark -c 10 -n 100000 -q
PING_INLINE: 101936.80 requests per second
PING_BULK: 102880.66 requests per second
SET: 95419.85 requests per second
GET: 104275.29 requests per second
INCR: 93283.58 requests per second
redis-cli monitor > /dev/null
):$ src/redis-benchmark -c 10 -n 100000 -q
PING_INLINE: 58479.53 requests per second
PING_BULK: 59136.61 requests per second
SET: 41823.50 requests per second
GET: 45330.91 requests per second
INCR: 41771.09 requests per second
MONITOR 不记录的命令
AUTH EXEC HELLO QUIT
5.2 通过 Redis的 hotkey 分析工具在服务端探测 HotKey:
redis-cli
提供了--hotkeys
选项,可以帮助分析哪些Key是热点Key。5.2.1 redis-cli提供了--hotkeys的用法
redis-cli --hotkeys
是一个Redis命令行工具的选项, 可以监视和查看当前活动的热点键(hot keys)。redis-cli --hotkeys
的示例:打开终端或命令提示符。 输入以下命令以启动 redis-cli
并使用--hotkeys
选项:
redis-cli --hotkeys
redis-cli
将连接到本地Redis服务器(默认端口为 6379)。$./redis-cli --hotkeys
# Scanning the entire keyspace to find hot keys as well as
# average sizes per key type. You can use -i 0.1 to sleep 0.1 sec
# per 100 SCAN commands (not usually needed).
[00.00%] Hot key 'counter:000000000002' found so far with counter 87
[00.00%] Hot key 'key:000000000001' found so far with counter 254
[00.00%] Hot key 'mylist' found so far with counter 107
[00.00%] Hot key 'key:000000000000' found so far with counter 254
[45.45%] Hot key 'counter:000000000001' found so far with counter 87
[45.45%] Hot key 'key:000000000002' found so far with counter 254
[45.45%] Hot key 'myset' found so far with counter 64
[45.45%] Hot key 'counter:000000000000' found so far with counter 93
-------- summary -------
Sampled 22 keys in the keyspace!
hot key found with counter: 254 keyname: key:000000000001
hot key found with counter: 254 keyname: key:000000000000
hot key found with counter: 254 keyname: key:000000000002
hot key found with counter: 107 keyname: mylist
hot key found with counter: 93 keyname: counter:000000000000
hot key found with counter: 87 keyname: counter:000000000002
hot key found with counter: 87 keyname: counter:000000000001
hot key found with counter: 64 keyname: myset
redis-cli --hotkeys
选项仅适用于Redis 6.2版本或更高版本。redis-cli --hotkeys
命令之前已经安装和正确配置了Redis,并且Redis服务器正在运行。5.2.2 redis-cli --hotkeys 的缺点
性能影响:由于它是一个全量的Hotkey数据,特别是存在大量hotkey的场景下会对性能产生较大影响,因此不推荐在生产环境频繁执行; 局限性:该命令返回的结果是基于Redis自身内部的采样与统计算法,根据机器资源的或预期场景的不同,该结果可能并不是100%符合预期的; 完整性:该命令只提供了热点键的基本信息,无法知道更详细的统计和分析信息,需要向业务侧确认;
6 代理端探测探测 HotKey:
7 在redis client 客户端探测 HotKey
7.1 大厂方案1: 透明多级缓存 (TMC) 架构设计
7.1.1 透明多级缓存 (TMC)简介
应用层热点探测 应用层本地缓存 应用层缓存命中统计
有赞 为什么要做 TMC
活动时间、活动类型、活动商品之类的信息不可预期,导致 缓存热点访问 情况不可提前预知; 缓存热点访问 出现期间,应用层少数 热点访问 key 产生大量缓存访问请求:冲击分布式缓存系统,大量占据内网带宽,最终影响应用层系统稳定性;
多级缓存解决方案的痛点
热点探测:如何快速且准确的发现 热点访问 key ? 数据一致性:前置在应用层的本地缓存,如何保障与分布式缓存系统的数据一致性? 效果验证:如何让应用层查看本地缓存命中率、热点 key 等数据,验证多级缓存效果? 透明接入:整体解决方案如何减少对应用系统的入侵,做到快速平滑接入?
7.1.2 透明多级缓存 (TMC)整体架构
存储层:提供基础的kv数据存储能力,针对不同的业务场景选用不同的存储服务( codis / zankv / aerospike ); 代理层:为应用层提供统一的缓存使用入口及通信协议,承担分布式数据水平切分后的路由功能转发工作; 应用层:提供统一客户端给应用服务使用,内置“热点探测”、“本地缓存”等功能,对业务透明;
7.1.3 透明多级缓存 (TMC)本地SDK缓存
如何实现透明 ?
基于 spring.data.redis
包,使用RedisTemplate
编写业务代码;基于 youzan.framework.redis
包,使用RedisClient
编写业务代码;
JedisPool
创建的Jedis
对象与缓存服务端代理层做请求交互。JedisPool
和Jedis
类做了改造,Hermes-SDK
包的初始化逻辑,Jedis
客户端与缓存服务端代理层交互时, 先与Hermes-SDK
交互,从而完成 “热点探测”+“本地缓存”功能的透明接入。7.1.4 透明多级缓存 (TMC)本地SDK缓存 整体结构
7.1.5 透明多级缓存 (TMC)本地SDK 模块划分
Jedis-Client: Java 应用与缓存服务端交互的直接入口,接口定义与原生 Jedis-Client 无异; Hermes-SDK:自研“热点发现+本地缓存”功能的SDK封装, Jedis-Client 通过与它交互来集成相应能力; Hermes服务端集群:接收 Hermes-SDK 上报的缓存访问数据,进行热点探测,将热点 key 推送给 Hermes-SDK 做本地缓存; 缓存集群:由代理层和存储层组成,为应用客户端提供统一的分布式缓存服务入口; 基础组件: etcd 集群、 Apollo 配置中心,为 TMC 提供“集群推送”和“统一配置”能力;
7.1.6 透明多级缓存 (TMC)本地SDK 基本流程
Java 应用调用 Jedis-Client 接口获取key的缓存值时,Jedis-Client 会询问 Hermes-SDK 该 key 当前是否是 热点key; 对于 热点key ,直接从 Hermes-SDK 的 热点模块 获取热点 key 在本地缓存的 value 值,不去访问 缓存集群 ,从而将访问请求前置在应用层; 对于非 热点key ,Hermes-SDK 会通过 Callable
回调 Jedis-Client 的原生接口,从 缓存集群 拿到 value 值;对于 Jedis-Client 的每次 key 值访问请求,Hermes-SDK 都会通过其 通信模块 将 key访问事件 异步上报给 Hermes服务端集群 ,以便其根据上报数据进行“热点探测”;
Java 应用调用 Jedis-Client 的 set()
del()
expire()
接口时会导致对应 key 值失效,Jedis-Client 会同步调用 Hermes-SDK 的invalid()
方法告知其“ key 值失效”事件;对于 热点key ,Hermes-SDK 的 热点模块 会先将 key 在本地缓存的 value 值失效,以达到本地数据强一致。同时 通信模块 会异步将“ key 值失效”事件通过 etcd集群 推送给 Java 应用集群中其他 Hermes-SDK 节点; 其他Hermes-SDK节点的 通信模块 收到 “ key 值失效”事件后,会调用 热点模块 将 key 在本地缓存的 value 值失效,以达到集群数据最终一致;
Hermes服务端集群 不断收集 Hermes-SDK上报的 key访问事件,对不同业务应用集群的缓存访问数据进行周期性(3s一次)分析计算,以探测业务应用集群中的热点key列表; 本地预热:对于探测到的热点key列表,Hermes服务端集群 将其通过 etcd集群 推送给不同业务应用集群的 Hermes-SDK 通信模块 ,通知其对热点key列表进行本地缓存 预热;
Hermes-SDK 在启动及运行过程中,会从 Apollo配置中心 读取其关心的配置信息(如:启动关闭配置、黑白名单配置、etcd地址...); Hermes服务端集群 在启动及运行过程中,会从 Apollo配置中心 读取其关心的配置信息(如:业务应用列表、热点阈值配置、 etcd 地址...);
7.1.7 透明多级缓存 (TMC)本地SDK 稳定性
数据上报异步化:Hermes-SDK 使用 rsyslog技术
对“ key 访问事件”进行异步化上报,不会阻塞业务;通信模块线程隔离:Hermes-SDK 的 通信模块 使用独立线程池+有界队列,保证事件上报&监听的I/O操作与业务执行线程隔离,即使出现非预期性异常也不会影响基本业务功能; 缓存管控:Hermes-SDK 的 热点模块 对本地缓存大小上限进行了管控,使其占用内存不超过 64MB(LRU),杜绝 JVM 堆内存溢出的可能;
7.1.8 透明多级缓存 (TMC)本地SDK 一致性
Hermes-SDK 的 热点模块 仅缓存 热点key 数据,绝大多数非热点 key 数据由 缓存集群 存储; 热点key 变更导致 value 失效时,Hermes-SDK 同步失效本地缓存,保证 本地强一致; 热点key 变更导致 value 失效时,Hermes-SDK 通过 etcd集群 广播事件,异步失效业务应用集群中其他节点的本地缓存,保证 集群最终一致;
7.1.9 TMC热点探测流程
TMC热点探测 整体流程
数据收集:收集 Hermes-SDK 上报的 key访问事件; 热度滑窗:对 App 的每个 Key ,维护一个时间轮,记录基于当前时刻滑窗的访问热度; 热度汇聚:对 App 的所有 Key ,以 <key,热度>
的形式进行 热度排序汇总;热点探测:对 App ,从 热Key排序汇总 结果中选出 TopN的热点Key ,推送给 Hermes-SDK;
7.1.9.1 数据收集
rsyslog
将 key访问事件 以协议格式放入 kafka ,Hermes服务端集群 的每个节点消费 kafka 消息,实时获取 key访问事件。appName:集群节点所属业务应用 uniqueKey:业务应用 key访问事件 的 key sendTime:业务应用 key访问事件 的发生时间 weight:业务应用 key访问事件 的访问权值
Map<String, Map<String, LongAdder>>
,Map< appName , Map< uniqueKey , 热度 >>
。7.1.9.2 热度滑窗
1: 时间滑窗
时间轮中共10个 时间片,每个时间片记录当前 key 对应 3 秒时间周期的总访问次数; 时间轮10个时间片的记录累加即表示当前 key 从当前时间向前 30 秒时间窗口内的总访问次数;
2. 映射任务
对当前 App ,从 Map< appName , Map< uniqueKey , 热度 >>
中取出 appName 对应的MapMap< uniqueKey , 热度 >>
;遍历 Map< uniqueKey , 热度 >>
中的 key ,对每个 key 取出其热度存入其 时间轮 对应的时间片中;
7.1.9.3 热度汇聚
遍历 App 的 key ,将每个 key 的 时间轮 热度进行汇总(即30秒时间窗口内总热度)得到探测时刻 滑窗总热度; 将 < key , 滑窗总热度 >
以排序集合的方式存入 Redis存储服务 中,即 热度汇聚结果;
7.1.9.4 热点探测
在前几步,每3秒 一次的 映射任务 执行,对每个 App 都会产生一份当前时刻的 热度汇聚结果; Hermes服务端集群 中的“热点探测”节点,对每个 App ,只需周期性从其最近一份 热度汇聚结果 中取出达到热度阈值的 TopN 的 key 列表,即可得到本次探测的 热点key列表;
7.1.10 TMC热点探测 特性总结
1. 实时性
2. 准确性
3.扩展性
7.1.11 TMC实战效果
7.1.11.1 快手商家某次商品营销活动
上图蓝线为应用集群调用 get()
方法访问缓存次数上图绿线为获取缓存操作命中 TMC 本地缓存的次数
上图为本地缓存命中率曲线图
7.1.11.2 热点缓存对应用访问的加速效果
上图为应用接口QPS曲线
上图为应用接口RT曲线
7.1.11.3 双十一期间部分应用 TMC 效果展示
1. 商品域核心应用效果
5-2-2. 活动域核心应用效果
7.1.12 TMC功能的总结
Canal使用场景2: 三级缓存的 数据一致性 通知
7.2 大厂方案2: 结合开源hotkey,做热点探测和预加载
特性 | 没有hotkey探测和预加载的缓存 | 使用hotkey探测和预加载的缓存 |
说在最后:有问题找老架构取经
被裁之后, 空窗1年/空窗2年, 如何 起死回生 ?
案例1:42岁被裁2年,天快塌了,急救1个月,拿到开发经理offer,起死回生
案例2:35岁被裁6个月, 职业绝望,转架构急救上岸,DDD和3高项目太重要了
案例3:失业15个月,学习40天拿offer, 绝境翻盘,如何实现?
被裁之后,100W 年薪 到手, 如何 人生逆袭?
100W案例,100W年薪的底层逻辑是什么? 如何实现年薪百万? 如何远离 中年危机?
如何 逆天改命,包含AI、大数据、golang、Java 等
实现职业转型,极速上岸
关注职业救助站公众号,获取每天职业干货
助您实现职业转型、职业升级、极速上岸
---------------------------------
实现架构转型,再无中年危机
关注技术自由圈公众号,获取每天技术千货
一起成为牛逼的未来超级架构师
几十篇架构笔记、5000页面试宝典、20个技术圣经
请加尼恩个人微信 免费拿走
暗号,请在 公众号后台 发送消息:领电子书
如有收获,请点击底部的"在看"和"赞",谢谢