多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

Redis为什么快?从内存存储到单线程模型再到多线程I/O的深度解析

Redis为什么快?从内存存储到单线程模型再到多线程I/O的深度解析 在互联网后端这个圈子里Redis 几乎成了“快”的代名词。不管是做缓存、做队列、做分布式锁还是做排行榜、计数器Redis 都能在毫秒级甚至微秒级返回结果。很多人第一次接触 Redis 时都会被它的性能震撼到但同时也会产生一个经典疑问为什么 Redis 能这么快而近几年围绕“Redis 多线程”的讨论又给这个问题添了一把火单线程的 Redis 不是已经够快了吗为什么又要引入多线程这篇内容我就基于自己的使用和源码阅读经验把 Redis 的性能底牌和线程模型一次性讲清楚。覆盖三块核心内容第一Redis 快在哪些维度、背后分别靠什么机制第二Redis 传统的单线程事件模型究竟是怎么跑起来的为什么当年选择单线程第三Redis 6.0 引入的多线程 I/O 到底解决了什么问题、启用后需要注意什么。文章会比较长但每部分都能直接落地适合正在学 Redis 的开发者、准备面试的同学以及想在项目中合理配置 Redis 性能参数的后端工程师。1. 先搞清楚 Redis 快在哪些维度1.1 单看数据Redis 的性能上限有多高要理解 Redis 为什么快先要对“快”这个字有个量化认知。在一台普通配置的云服务器上Redis 的 SET、GET 这类简单命令单实例 QPS 通常能做到 8 万到 12 万如果使用 pipeline 批量提交吞吐可以轻松冲到几十万甚至上百万 QPS。这个量级下一条命令的平均响应时间大概在 0.1 毫秒以内很多场景下是亚毫秒级返回。对比一下传统的关系型数据库MySQL 在相同硬件条件下单表单机简单查询的 QPS 大概在几千到一两万响应时间通常在 1 毫秒到 10 毫秒量级。即便经过各种连接池、缓存优化纯磁盘数据库也很难在延迟上和 Redis 正面硬刚。这也是为什么所有高并发系统里Redis 几乎成了缓存层的标配。不过这里要强调一点Redis 快不代表所有操作都一样快。比如 KEYS 命令在全量大 key 下会阻塞、SORT 在复杂排序条件下也不会太轻松、大 key 的删除操作在旧版本中甚至会造成秒级卡顿。所以准确说Redis 是在“正确的命令 合理的数据结构设计”前提下才做到极致性能的。1.2 快不是单一原因造成的很多人把 Redis 的快简单归因于“单线程”这其实是误解。线程模型只是其中一环Redis 的极高性能是多种因素叠加的结果。我习惯把它的性能底牌拆成四层第一层数据存放在内存里读写不碰磁盘这是性能的物理基础第二层底层数据结构被精心设计过不同数据类型对应不同的编码方式在时间和空间上反复做了权衡第三层I/O 多路复用加单线程事件循环避免了线程上下文切换和并发竞争带来的额外开销第四层源码层面的极致优化比如避免了大量内存拷贝、使用自己实现的内存分配器、渐进式 rehash 平滑扩张等。这四层缺一不可。只靠内存那所有 KV 数据库都能快只靠单线程那 Node.js 也早就统一世界了。真正拉开差距的是这套组合拳打下来的整体效果。下面我按这个框架一层一层拆开来讲。2. 数据全内存Redis 快的地基2.1 内存访问为什么比磁盘快几个数量级Redis 的核心数据全部存放在内存里读写过程就是操作内存中的数据结构。内存的随机访问延迟通常在 80 纳秒到 120 纳秒这个级别而一块普通 SSD 的随机读延迟大概在 50 微秒到 150 微秒传统机械硬盘则要去到 5 毫秒到 10 毫秒。这中间的差距有多大内存比 SSD 快大约 500 倍比机械硬盘快约 5 万倍。所以从存储介质层面Redis 的响应速度已经和数据库不在一个量级上。这也是“Redis 为什么快”这个问题最底层、最朴素的答案。当然光内存还不够。很多数据库也有内存缓冲池比如 MySQL 的 Buffer Pool热门数据也会缓存在内存里。但 MySQL 的默认存储引擎 InnoDB 仍然是面向磁盘页设计的数据最终要落地事务要刷 redo log崩溃恢复要处理脏页。而 Redis 的持久化更像是一个“可选的备份机制”默认情况下所有读写都在内存完成持久化对主链路几乎没有影响。2.2 内存存储带来的取舍内存存储不是没有代价最直接的问题有两个成本高、容量有限。同样容量的内存比磁盘贵得多而且单台服务器的内存插槽有限Redis 实例的数据量一旦大到内存装不下就得考虑集群分片或者淘汰策略。在实际项目中我一直建议团队给 Redis 规划容量时留足余量至少保留 20% 到 30% 的空闲内存原因有两个一是 RDB 持久化时 fork 子进程需要 copy-on-write 内存二是内存碎片和临时内存峰值都可能让实际占用比理论值高不少。如果 Redis 开始走操作系统的 swap那性能会瞬间崩到磁盘级别“快”这个字就彻底不存在了。3. 数据结构与编码快不只是因为内存3.1 五种基本类型和底层实现的对应关系如果把 Redis 的快速简单归结为“用内存”那有点暴殄天物。Redis 在数据结构上的功夫才是它能在同样用内存的 KV 系统里脱颖而出的原因。Redis 对外暴露的基本数据类型有五种String、Hash、List、Set、ZSet。每一种类型底层都不是一套固定实现而是根据元素数量和元素大小在多种编码之间动态切换。我把自己做的底层结构对照表放在这里类型底层编码触发条件Stringint / embstr / raw纯整数用 int短字符串用 embstr长字符串用 rawHashziplist7.0 后为 listpack/ hashtable元素少且值小时用压缩编码超过阈值转 hashtableListquicklist由多个 ziplist 节点组成的链表结构Setintset / hashtable全整数且数量少用 intset否则用 hashtableZSetziplist7.0 后为 listpack/ skiplist hashtable元素少时用压缩编码超过阈值转跳表这种动态编码策略的核心目的是在数据量小的时候用连续内存的紧凑编码节省空间并提升缓存命中率在数据量大的时候切换到时间效率更高的结构。ziplist 虽然需要移动内存来插入数据但当单个 key 的元素只有几十个时这种移动成本极低反而比维护一个完整哈希表的开销更小。3.2 跳表和哈希表为什么适合做高性能索引ZSet 是 Redis 里最具代表性也最常被问的数据结构。它要在支持按分值快速查找的同时还要支持范围查询和按排名获取元素。如果只用一个哈希表范围查询做不了如果只用一个有序链表查找又变成线性。Redis 的方案是跳表加哈希表的组合。哈希表负责 O(1) 地根据 member 查 score跳表负责按顺序维护所有元素。跳表的本质是在有序链表上增加多级索引查找时从最高层开始跳跃式下降时间复杂度为 O(log N)。它比平衡树实现更简单不需要复杂的旋转操作在并发写少、单线程执行命令的环境里更利于维护和调试。我经常在面试中把一个 Redis 性能问题拆成数据结构问题来问因为能答清楚“为什么 ZSet 用跳表而不用红黑树”的人大概率是真的读过源码而不是只背了八股。原因总结下来有三点跳表实现更简单、范围查询友好、内存占用可接受。3.3 渐进式 rehash把卡顿风险拆小哈希表在扩容时如果一次性完成在数据量大的场景下会造成明显的命令堵塞。Redis 的处理方式叫渐进式 rehash扩容时旧表和新表同时存在每次对哈希表执行增删改查时顺手把旧表的一小部分桶迁移到新表分摊到后续多次访问中完成。这个设计虽然让代码复杂度上了一个台阶但换来的收益是 Redis 在扩容过程中依然能保持稳定的响应。类似这种“把大操作拆小”的思维在 Redis 源码里到处可见比如大 key 的延迟删除、UNLINK 命令、4.0 引入的 lazy free 机制都是同一思路。4. 单线程事件循环Redis 线程模型的核心4.1 文件事件处理器的组成Redis 的服务端是一个典型的事件驱动程序核心组件叫文件事件处理器File Event Handler。它由四部分构成多个 socket 连接、I/O 多路复用程序、文件事件分派器、事件处理器命令请求处理器、命令回复处理器、连接应答处理器。整个流程可以概括成多个客户端连接通过 socket 把请求发过来I/O 多路复用程序同时监听这些 socket 上的事件当某个 socket 变得可读或可写时多路复用程序把对应事件交给分派器分派器根据事件类型调用事先注册好的处理器处理完成后继续回到等待状态。这里的关键在于所有事件处理都是在一个线程内完成的所以叫单线程模型。注意Redis 单线程指的是处理命令请求和回复的网络 I/O 以及命令执行都在主线程完成而不是整个进程只有一个线程。Redis 内部还有后台线程负责持久化、异步删除、AOF 重写等任务。4.2 事件循环到底是怎么转起来的用伪代码来理解会更直观。Redis 主线程的主循环大致长这样while (1) { // 阻塞等待事件发生超时时间由 aeApiPoll 决定 aeApiPoll(eventLoop, tvp); // 处理所有已触发的文件事件 for (fileEvent in firedEvents) { if (fileEvent AE_READABLE) { // 根据 socket 类型调用读处理器 // 如果是新连接调用 acceptTcpHandler // 如果是普通客户端调用 readQueryFromClient } if (fileEvent AE_WRITABLE) { // 调用 sendReplyToClient把输出缓冲区数据写回客户端 } } // 处理时间事件比如 serverCron 定时任务 processTimeEvents(); }这套循环本身不复杂复杂的是如何保证单线程下多个连接的公平性和实时性。Redis 在 ae_epoll.c 中使用 epoll 时每次通过 epoll_wait 拿到就绪文件描述符列表再逐个处理。如果没有就绪事件就会阻塞在 epoll_wait 上不会空转消耗 CPU。4.3 epoll 为什么比 select 和 poll 强I/O 多路复用不是 Redis 的发明但 Redis 把它用到了极致。常见的多路复用系统调用有三种select、poll、epoll。select 的问题在于单个进程能监听的 fd 数量有限制一般是 1024而且每次调用都要把 fd 集合从用户态拷贝到内核态内核还要线性扫描全部 fd连接一多性能就会严重下降。poll 解决了 fd 数量限制但仍然是每次全量拷贝加线性扫描。epoll 的改进是革命性的内核维护一个事件表应用通过 epoll_ctl 注册 fd不需要每次重新传入全部 fdepoll_wait 只返回触发事件的 fd不需要遍历所有连接配合 mmap 加速内核与用户空间的消息传递效率比 select 高一个量级。Redis 对不同平台做了抽象Linux 上用 epollmacOS/BSD 上用 kqueueWindows 的版本则用 select 模拟这也是为什么官方不推荐在生产环境用 Windows 版 Redis 的原因之一。4.4 单线程到底有什么好处当年 Redis 作者选择单线程不是拍脑袋而是基于几个实打实的好处不需要处理锁竞争。所有命令在同一个线程里串行执行天然不存在多线程对共享数据结构的竞争问题代码里不需要加锁也不会出现死锁、活锁这些并发难题。避免了线程切换开销。CPU 在多个线程间切换是有代价的包括保存和恢复上下文、刷新流水线。对 Redis 这种处理逻辑极短的操作线程切换成本甚至可能超过命令本身。实现和维护难度大幅降低。很多复杂的并发 bug 根本不会出现也让 Redis 的代码库保持了非常高的可读性方便全世界开发者参与贡献。对于内存数据结构来说串行执行天然保证了原子性。单个命令不需要额外事务就能保证不会读到中间状态。但要澄清一点单线程限制的是“命令执行”阶段不是整个 Redis 进程只有一条线程。4.0 引入 lazy free 后Redis 有了后台线程来异步释放大对象6.0 引入多线程 I/O 后网络读写也可以分流给多个线程。所以准确的说法是Redis 的命令执行始终是单线程的但 I/O 和后台任务可以多线程。5. Redis 6.0 多线程为什么改怎么改5.1 单线程模型遇到的真瓶颈既然单线程这么好为什么 Redis 还要拥抱多线程答案其实藏在性能曲线里。当 Redis 作为纯缓存使用、数据量完全在内存中时主线程的 CPU 消耗主要集中在两方面一是执行命令本身二是网络 I/O读取请求、解析协议、写回响应。命令执行层面的耗时通常只有几十微秒但网络 I/O 在连接数非常多、请求包非常密集时read 和 write 系统调用的开销会被成倍放大。尤其是使用 Redis 的典型场景里客户端成千上万每个连接不断发送小包请求主线程需要反复从 socket 读取数据并写回数据这部分 CPU 占用会逐渐逼近上限。Redis 官方做过测试在千兆网卡跑满的情况下单线程处理网络 I/O 已经有些吃力。这里的瓶颈不是 CPU 不够快而是单线程能执行的系统调用次数有上限。这时候引入多线程本质上是把网络读写部分从主线程分流出去让主线程专心执行命令。5.2 多线程 I/O 的设计与实现思路Redis 6.0 的多线程方案非常克制它没有把整个命令处理流程变成多线程并发执行而只是把“读 socket 和解析请求”以及“写回响应数据”这两个阶段拆给多个 I/O 线程去做命令的核心执行仍然由主线程串行完成。具体的流水线大致是主线程通过 epoll 发现可读事件把产生事件的客户端连接放入待读取列表。主线程根据配置将待读列表均匀分发给多个 I/O 线程每个 I/O 线程负责读取自己拿到的连接上的数据并解析出完整命令。所有 I/O 线程完成读取后主线程开始逐个执行命令期间其他线程等待。命令执行完成后主线程把需要写回响应的客户端连接分发给 I/O 线程由多个线程并行 write。所有写回完成后主线程重新进入下一轮事件循环。这样设计的好处很明显命令执行的原子性没有被破坏不需要在处理命令时加锁同时网络 I/O 的吞吐能力有了质的提升。5.3 多线程带来的新问题和争议多线程不是免费的午餐。引入多线程 I/O 后Redis 也多了一些此前不需要担心的东西并发访问客户端连接对象需要加锁。多个 I/O 线程会同时操作客户端列表Redis 通过原子操作和锁来保护共享数据。调试变难。单线程时代很多问题用日志就能推演多线程时代则需要借助 GDB 的线程调试能力和更细致的日志。性能并非线性提升。I/O 线程越多线程调度和锁竞争的开销越大所以官方默认是关闭多线程的需要手动开启。只有网络读写受益命令执行依然是单线程。如果某个命令本身耗时严重多线程 I/O 救不了。还有一个很多人在意的问题多线程 Redis 是否会破坏命令的原子性答案是不会。因为所有命令的执行仍然在主线程串行完成I/O 线程只负责搬运数据不参与命令的执行逻辑。所以像 INCR、LPUSH 这类命令依旧具备原子性事务和 Lua 脚本的语义也没有改变。5.4 启用多线程的配置建议Redis 6.0 之后多线程 I/O 通过配置文件中的两个参数控制io-threads 4 io-threads-do-reads yesio-threads 指定线程数官方建议设为 CPU 核心数减一最好不要超过 8。io-threads-do-reads 默认是 no也就是说只开启多线程写回读仍然在主线程完成显式设置为 yes 后读取和解析也走多线程。不过我不建议一上来就无脑开启尤其是 io-threads-do-reads。我自己的实践经验是在连接数少、单连接吞吐高的场景里开启读多线程收益不大甚至因为锁和线程调度反而略有下降。只有当连接数非常多、大量小请求并发涌入时多线程 I/O 的优势才会明显体现。生产环境建议通过 redis-benchmark 做前后对比测试再决定是否启用。6. 生产环境中的性能实践6.1 正确使用 Benchmark 判断瓶颈不管 Redis 版本怎么变性能调优的第一步永远是“量化现状”。Redis 自带的 redis-benchmark 工具是最好用的压测入口。使用时要特别注意命令格式和场景匹配redis-benchmark -h 127.0.0.1 -p 6379 -c 200 -n 1000000 -t set,get -d 100 -P 16常用参数含义如下-c 200模拟 200 个并发连接-n 1000000总共发送 100 万条请求-t set,get只压测 set 和 get-d 100value 大小为 100 字节-P 16使用 pipeline每批打包 16 条命令我建议至少跑两组对比一组不开 pipeline一组开 pipeline。如果不开 pipeline 时 QPS 已经很高说明瓶颈在网络往返而不在 Redis 本身如果 pipeline 开了很多但 QPS 依然上不去再考虑检查 CPU、网卡以及是否命中大 key。6.2 慢查询日志和延迟监控Redis 的性能排查还要养成看慢查询日志的习惯。通过配置 slowlog-log-slower-than 和 slowlog-max-len可以把超过指定耗时的命令记录下来。slowlog-log-slower-than 10000 slowlog-max-len 128上面的配置表示记录执行时间超过 10 毫秒的命令。线上环境建议设置得更严格一些比如 5000 微秒甚至 2000 微秒。用 SLOWLOG GET 可以查看最近的慢查询。如果发现某个命令频繁出现在慢查询里基本可以断定存在大 key 或者使用了高复杂度的命令。另一个实用工具是 INFO commandstats它能统计每个命令的调用次数、总耗时和平均耗时定位哪些命令占了最多的 CPU。结合热点 key 的分析可以快速找到需要优化的对象。6.3 大 key 和小 key 的运维要点性能问题里最常见也最隐蔽的元凶就是大 key。一个包含几十万元素的 Hash、一个几兆字节的 String在读写时都可能造成主线程卡顿。尤其是一些时间复杂度看起来是 O(1) 的命令比如 HGETALL、LRANGE 0 -1、SMEMBERS一旦 value 巨大照样会拖垮 Redis。排查大 key 可以用内置命令 redis-cli --bigkeys它会扫描整个实例并输出每种类型里最大的 key。不过生产环境我建议用 scan 加类型判断的方式自己写脚本分批扫描避免阻塞。发现大 key 后的处理思路一般是拆分字段、压缩 value、或者用 UNLINK 异步删除而不是 DEL。6.4 缓存淘汰策略对性能的隐性影响Redis 的 maxmemory-policy 配置直接决定了内存达到上限时会发生什么。很多人只盯着 noeviction、allkeys-lru 这些名字却忽略了淘汰过程本身也是耗时的。volatile-lru 和 allkeys-lru 在淘汰时需要对候选 key 做近似 LRU 采样allkeys-random 则完全不计算。allkeys-lru 虽然在缓存场景下命中率更好但在极端情况下每次新写入触发淘汰时的计算也会成为延迟的一部分。如果使用 Redis 做严格缓存建议结合 maxmemory-samples 参数调整采样数量官方默认是 5调太高会明显增加淘汰耗时。我个人的实践习惯是配置 maxmemory 时留出 30% 的冗余并把淘汰策略设为 allkeys-lru尽量避免 Redis 因为内存达到上限而频繁执行淘汰。7. 面试视角如何把这个问题讲透7.1 一个完整的回答框架在很多技术面试里“Redis 为什么快”属于高频题。我建议的回答思路是“总分总”先说结论再说原因最后用例子收尾。结论Redis 快是一套组合机制的结果包括内存存储、高效数据结构、多路复用模型、单线程避免竞争以及源码层面的极致优化。然后按下面几个层次展开物理层数据在内存读写不碰磁盘延迟天然低。结构层动态编码、跳表、intset、listpack 等设计让内存占用和访问速度达到平衡。模型层文件事件处理器配合 epoll单线程无锁执行命令。工程层渐进式 rehash、lazy free、AOF 重写后台化等机制避免大操作阻塞主线程。最后补一句6.0 后引入多线程 I/O但命令执行仍是单线程所以原子性语义没有改变。7.2 面试官经常追问的延伸问题第一个追问是“为什么使用单线程反而更快”。这里要讲清楚多线程的收益主要来自多核并行计算和 I/O 等待期利用但 Redis 命令执行是内存内操作本身极快多线程引入的上下文切换和锁竞争成本反而可能超过并行收益。第二个追问是“多线程 Redis 是否意味着可以放心的执行耗时命令”。答案是否定的。因为命令执行仍是单线程任何耗时操作依然会阻塞其他所有命令。第三个追问是“Redis 单线程为什么还能支持十万级 QPS”。核心原因是 I/O 多路复用让大量连接的事件处理集中在一个循环内避免了频繁的系统调用配合非阻塞 I/O 和内存数据结构单个事件的处理时间被压缩到极短。7.3 结合版本演进回答更能加分如果面试官问到版本相关可以把演进脉络整理成时间线早期 Redis 全链路单线程靠多路复用扛性能4.0 引入后台线程做 lazy free 和 AOF 重写6.0 引入 I/O 多线程改变网络读写瓶颈7.0 继续优化用 listpack 替代 ziplist并对多线程 I/O 做了更多细节打磨。这样回答的好处是既展示了对历史版本的了解又体现出在持续跟进新版本。面试官很容易从你的叙述里判断出你是真正用过 Redis 的人而不是背了几篇博客就开始面。8. 常见问题与排查技巧实录8.1 问题一为什么开启 io-threads 后性能反而下降这是我在生产环境最常遇到的坑。开启多线程 I/O 后如果机器本身核数不多或者网络包不大线程之间的调度开销会抵消掉并行读写的收益。排查步骤先确认当前 Redis 版本的 CPU 使用情况用 top 查看主线程和 I/O 线程的 CPU 占用分布再用 redis-benchmark 对比开和不开多线程的 QPS。如果多线程场景下 QPS 反而低了优先调小 io-threads 数量或者把 io-threads-do-reads 重新设为 no。实践心得连接数少于 500、单连接持续大流量读写的场景不必开启多线程。多线程 I/O 最适用的场景是成千上万个连接同时发送小请求比如典型的缓存层高并发访问。8.2 问题二某个命令阻塞了主线程怎么定位线上 Redis 出现大面积超时首先要看是不是有慢命令导致其他所有命令排队。用 redis-cli --latency 可以查看实时延迟分布用 SLOWLOG GET 可以列出最近的慢命令。如果慢查询里全是 KEYS、SMEMBERS、HGETALL 这类全量操作基本就是命令使用不当。如果慢查询里没有内容但延迟依然很高要检查是不是大 key 过期删除导致的阻塞。因为过期 key 在主线程惰性删除时如果正好删到一个巨大的集合会拖住整个事件循环。这种情况下可以开启 lazyfree-lazy-expire 参数把过期删除放到后台线程。8.3 问题三Redis 延迟突刺的常见来源延迟突刺是最难排查的一类问题我整理过一张高频原因对照表症状可能原因排查方向延迟抖动无慢命令fork 生成 RDB 导致瞬间内存复制开销调整 RDB 保存频率使用主从节点做备份整体延迟升高内存不足触发 swap检查 free / vmstat确认 Redis 是否换页偶发超时CPU 不高大 key 被惰性删除扫描大 key开启 lazyfree多个客户端同时卡顿网卡软中断打满查看 /proc/softirqs考虑 RPS 调整特定命令频繁超时复杂命令或者超大数据量返回拆分 key使用增量遍历类命令延迟问题的定位思路永远是先排除 Redis 自身再看操作系统最后看网络和客户端。不要把锅都甩给 Redis很多时候问题出在客户端连接池配置不合理或者 GC 停顿导致客户端线程阻塞。8.4 问题四如何使用 INFO 快速评估健康状态INFO 命令的输出我觉得最有价值的是这几个维度connected_clients连接数是否异常增长used_memory 和 used_memory_peak内存是否逼近上限total_commands_processed每秒命令处理量是否存在波动instantaneous_ops_per_sec当前实时吞吐blocked_clients是否有客户端被阻塞通常意味着有慢命令或者事务问题evicted_keys如果这个值持续增长说明内存压力很大生产环境建议写个定时脚本每分钟采集一次 INFO 关键指标配合监控平台做告警。Redis 本身没有太多自我调优的能力很多问题要靠外部监控提前发现。9. 给刚上手 Redis 的人三个建议第一多读源码胜过背面试题。很多人能背出 Redis 用 epoll 是因为快但从来没有看过 ae.c 和 networking.c。我强烈建议花一个周末把这两份文件过一遍比刷十篇博客都有效。读源码不需要全部懂能把事件循环的主干走通就足够了。第二压测是一门手艺活不要只看 QPS 数字。同一台机器不同版本的 Redis、不同客户端 SDK甚至不同 GCC 版本编译出来的二进制性能差异都很大。每次调优只改变一个变量否则你永远不知道是哪个参数起了作用。第三不要神话任何技术。Redis 再快也有自己的边界内存容量有限、单线程命令执行对大 key 敏感、持久化配置不当会丢数据。理解一个技术的边界比知道它多强更重要。我自己的习惯是每接触一个新版本先把发布说明里的性能优化部分读完再在自己测试环境跑一遍主要场景的压测。Redis 这个项目在性能上的最大优势其实是它始终没有忘记自己“快”这个初心——即便引入了多线程也只是为了让快更快而不是为了追潮流把架构搞复杂。这点对我们做技术选型和写代码都有借鉴意义。
返回列表