文章目录
-
- [为什么需要 Redis:从单机到分布式的架构演进之路](#为什么需要 Redis:从单机到分布式的架构演进之路)
- 一、前言
- 二、基本概念
-
- [2.1 应用、模块与组件](#2.1 应用、模块与组件)
- [2.2 分布式与集群](#2.2 分布式与集群)
- [2.3 主(Master)与从(Slave)](#2.3 主(Master)与从(Slave))
- [2.4 中间件](#2.4 中间件)
- [2.5 衡量系统好坏的三个指标](#2.5 衡量系统好坏的三个指标)
- 三、阶段一:单机架构
-
- [3.1 最开始的样子](#3.1 最开始的样子)
- [3.2 单机架构的问题](#3.2 单机架构的问题)
- 四、阶段二:应用与数据分离
-
- [4.1 最低成本的第一步拆分](#4.1 最低成本的第一步拆分)
- [4.2 新的瓶颈:单台应用服务器撑不住了](#4.2 新的瓶颈:单台应用服务器撑不住了)
- [五、阶段三:应用集群 + 负载均衡](#五、阶段三:应用集群 + 负载均衡)
-
- [5.1 多台应用服务器协同工作](#5.1 多台应用服务器协同工作)
- [5.2 常见的负载均衡调度算法](#5.2 常见的负载均衡调度算法)
- [5.3 新的瓶颈:数据库撑不住了](#5.3 新的瓶颈:数据库撑不住了)
- 六、阶段四:读写分离
-
- [6.1 主从分离的核心思路](#6.1 主从分离的核心思路)
- [6.2 为什么读写分离有效](#6.2 为什么读写分离有效)
- [6.3 新的瓶颈:热点数据反复查数据库](#6.3 新的瓶颈:热点数据反复查数据库)
- [七、阶段五:引入缓存 ------ Redis 登场](#七、阶段五:引入缓存 —— Redis 登场)
-
- [7.1 冷热数据的区别](#7.1 冷热数据的区别)
- [7.2 引入缓存后的架构](#7.2 引入缓存后的架构)
- [7.3 为什么选 Redis 而不是 Memcached](#7.3 为什么选 Redis 而不是 Memcached)
- [7.4 引入缓存带来的新问题](#7.4 引入缓存带来的新问题)
- 八、阶段六:数据库分库分表
-
- [8.1 数据量继续爆炸](#8.1 数据量继续爆炸)
- [8.2 分库分表的代价](#8.2 分库分表的代价)
- 九、阶段七:微服务架构
-
- [9.1 业务拆分的必要性](#9.1 业务拆分的必要性)
- [9.2 Redis 在微服务里的位置](#9.2 Redis 在微服务里的位置)
- 十、总结
为什么需要 Redis:从单机到分布式的架构演进之路
一、前言
💬 这一篇讲什么:在正式学习 Redis 之前,先搞清楚它是怎么"被逼出来"的
🚀 核心内容:
- 什么是分布式系统?常见概念有哪些?
- 一个电商系统是如何从单机一步步演进到分布式架构的?
- 每个阶段遇到了什么瓶颈,用什么方案解决?
- Redis 究竟在哪个环节登场,解决了什么问题?
很多人学 Redis 上来就背命令、背数据类型,学了半天却不明白它为什么存在,用在哪,解决了什么问题。这一篇不写任何 Redis 命令,只讲一件事:一个真实的互联网系统,是如何一步步把 Redis 逼出来的。
二、基本概念
2.1 应用、模块与组件
应用(Application)/ 系统(System):为了完成一整套服务而搭建的程序或程序群。比如淘宝就是一个应用,它背后是无数个相互配合的程序在支撑运转。
模块(Module)/ 组件(Component):当应用复杂到一定程度,会把其中职责清晰、内聚性强的部分单独抽出来,方便理解和维护。就像一支军队会分为突击小组、后勤小组、通信小组------各司其职,互不干扰。
2.2 分布式与集群
分布式(Distributed):系统中的多个模块被部署在不同的服务器上,彼此通过网络通信配合完成任务。比如 Web 服务器和数据库分别跑在两台机器上,这就是分布式。
集群(Cluster):部署在多台服务器上的、为了完成同一个目标的一组组件,整体称为集群。比如三台机器都跑 MySQL,共同提供数据库服务,这就是数据库集群。
两者不用太严格区分:分布式强调物理形态 (运行在不同机器上),集群强调逻辑目标(为了同一个服务目标而协作)。
2.3 主(Master)与从(Slave)
集群里通常有一台机器承担更多职责,称为主节点 ;其他承担辅助职责的称为从节点。比如 MySQL 集群中,只有主库允许写入数据(增删改),从库的数据全部从主库同步过来,只对外提供读取。
2.4 中间件
中间件(Middleware):处于不同应用程序之间、提供通信桥梁的一类软件。打个比方,一家餐厅起初每天自己去菜市场买菜,规模大了之后成立专门的采购部,采购部就是厨房和菜市场之间的"中间件"。在软件里,Redis、Nginx、MyCat 等都属于中间件的范畴。
2.5 衡量系统好坏的三个指标
可用性(Availability):单位时间内系统能正常提供服务的概率。我们常说的"4个9"就是 99.99% 的可用性,换算下来一年中最多允许宕机约 52 分钟;"5个9"则只允许宕机约 5 分钟。
响应时长(Response Time,RT):用户发出请求到收到结果的时间。原则上越短越好,但很多场景下要根据实际情况权衡。
吞吐量 / 并发(Throughput / Concurrent):吞吐量是单位时间内成功处理的请求数;并发是系统同一时刻能承载的最高请求量。我们平时说的"高并发"就是追求这个指标。
好,概念铺垫完毕,下面进入正题。
三、阶段一:单机架构
3.1 最开始的样子
任何系统在刚起步时都是单机架构。假设我们要做一个电商网站,初期团队小、预算紧、用户少,整个系统就跑在一台服务器上:
text
用户
↓
[单台服务器]
├── 应用服务(处理业务逻辑:用户、商品、交易)
└── 数据库服务(存储所有数据)
├── 用户表
├── 商品表
└── 交易表
用户在浏览器输入 www.shop.com,DNS 把域名解析成 IP 地址,浏览器访问那台服务器,服务器处理完业务逻辑后从数据库读写数据,最后把结果返回给用户。
这个阶段技术栈很简单:一个 Web 服务器软件(Tomcat、Nginx 等)加一个数据库(MySQL 等),搭起来就能跑。大多数同学在学校做的课程设计、毕业设计,基本都是这个阶段的架构。
3.2 单机架构的问题
单机的核心问题是:应用和数据库共享同一台机器的 CPU、内存、磁盘。随着访问量增加,两者会互相抢占资源。应用逻辑跑得慢,数据库也跑得慢,整体性能很快触顶,迟早需要升级。
四、阶段二:应用与数据分离
4.1 最低成本的第一步拆分
网站上线后逐渐积累了一批稳定用户,访问量开始上升,单机开始撑不住。此时预算依然有限,最低成本的优化方案是:把应用服务和数据库服务分开,分别部署到两台服务器上。
text
用户
↓
[应用服务器] [存储服务器]
应用服务 ─── 网络 ─── 数据库服务
(处理业务逻辑) ├── 用户表
├── 商品表
└── 交易表
两台机器各司其职,不再互相抢资源,系统的承载能力得到明显提升,而且改动成本很低,只需要把数据库迁移到另一台机器上即可。
4.2 新的瓶颈:单台应用服务器撑不住了
随着业务继续增长,出现了爆款商品,流量暴增,单台应用服务器又触顶了。这时候摆在面前的有两条路:
垂直扩展(Scale Up):花大钱换一台性能更强的服务器。优点是不需要改代码;缺点是硬件性能和价格不是线性关系------性能翻倍,价格可能翻四倍,而且硬件性能本身存在物理上限。
水平扩展(Scale Out):多加几台同等规格的普通服务器,把流量分摊开。优点是成本可控,扩展上限高;缺点是系统复杂度增加,需要解决"流量怎么分"的问题。
长期来看,水平扩展才是正确的路。
五、阶段三:应用集群 + 负载均衡
5.1 多台应用服务器协同工作
水平扩展之后有了多台应用服务器,但随之而来一个问题:用户的请求该发给哪台?这就需要一个负载均衡器来统一接收请求,再按照一定的策略分发出去。
text
用户
↓
[负载均衡器]
├── [应用服务器 1]
├── [应用服务器 2] ─── 网络 ─── [数据库服务器]
└── [应用服务器 3]
5.2 常见的负载均衡调度算法
负载均衡器该怎么分发请求?常见的策略有以下几种:
轮询(Round-Robin):依次轮流,非常公平。请求1给服务器1,请求2给服务器2,请求3给服务器3,然后循环。
加权轮询(Weight-Round-Robin):给性能更强的服务器分配更高的权重,能者多劳。比如服务器1性能是服务器2的两倍,就让它处理两倍的请求量。
一致性哈希:根据用户特征(比如 IP 地址)计算哈希值,相同特征的用户总是被路由到同一台服务器。这就像银行的专属客户经理服务,你每次打电话都对接同一个人,状态和上下文可以延续。
这个阶段用到的技术:Nginx、HAProxy、LVS 等负载均衡软件。
5.3 新的瓶颈:数据库撑不住了
应用层通过水平扩展解决了流量问题,但无论加多少台应用服务器,所有请求最终都会落到那一台数据库上。数据库很快成为整个系统的新瓶颈。
那能不能像扩展应用服务器一样,直接加多台数据库来分摊压力?
不能。 数据库有特殊性------如果数据随意分散在多台服务器上,就无法保证数据的一致性。想象这个场景:用户 A 向用户 B 转账 100 元,A 的余额在数据库1上减了 100,但 B 的余额在数据库2上没有加上,这 100 元就凭空消失了。所以数据库的扩展需要更谨慎的设计。
六、阶段四:读写分离
6.1 主从分离的核心思路
解决数据库瓶颈的方案是读写分离 :保留一台主库 负责所有的写操作(增删改),其余的从库数据全部从主库同步过来,只负责响应读请求。
text
用户
↓
[负载均衡器]
├── [应用服务器 1]
├── [应用服务器 2]
└── [应用服务器 3]
│
├── 写请求 ──→ [主数据库]
│ ↓ 数据同步
└── 读请求 ──→ [从数据库 1]
──→ [从数据库 2]
──→ [从数据库 3]
6.2 为什么读写分离有效
绝大多数互联网系统的读写请求比例都是严重不对称的。以电商为例,可能 100 次操作里有 95 次是读(浏览商品、查订单),只有 5 次是写。把读压力分散到多台从库,主库只承担写入,整体压力就大幅降低了。
当然这个方案不是没有代价的:主库到从库的数据同步存在一定的时间延迟,也就是说从库的数据会短暂地落后于主库。大部分场景下这个延迟可以接受,但对于强一致性要求高的场景(比如支付结果查询)就需要额外处理。
这个阶段用到的技术:MyCat、TDDL、Amoeba 等数据库中间件,负责自动将读写请求路由到对应的库。
6.3 新的瓶颈:热点数据反复查数据库
读写分离之后系统又撑了一段时间,但新的问题浮现出来:有些数据被反复查询,每次都要走数据库,哪怕数据根本没有变化。
比如电商首页的爆款商品信息,每秒可能被查询几十万次,而这些数据几个小时都不会变一次。每次查询都打到数据库,既慢、又浪费,还在白白消耗数据库的连接资源。
这个问题靠继续加从库已经解决不了了,需要引入新的解决思路。
七、阶段五:引入缓存 ------ Redis 登场
7.1 冷热数据的区别
这时候我们意识到,数据可以按照访问频率分成两类:
热数据:被频繁读取、但不常变化的数据。比如商品基本信息、首页推荐列表、用户 Session。
冷数据:不常被访问的数据。比如用户几年前的历史订单、归档日志。
对于热数据,每次都去数据库查完全没必要。把它放进内存缓存起来,直接从内存读,速度比磁盘快几十倍乃至上百倍,同时数据库的压力也大幅降低。
7.2 引入缓存后的架构
text
用户
↓
[负载均衡器]
├── [应用服务器 1]
├── [应用服务器 2]
└── [应用服务器 3]
│
├── 先查缓存 ──→ [Redis 缓存服务器] ← 命中直接返回结果
│ ↑ 未命中时回填
├── 写请求 ──→ [主数据库]
│ ↓ 同步
└── 读请求 ──→ [从数据库]
核心逻辑 很简单:应用处理读请求时,先去 Redis 查。如果 Redis 里有(缓存命中 ),直接返回,整个流程不碰数据库;如果 Redis 里没有(缓存未命中),再去数据库查,查完把结果写进 Redis,下次同样的请求就直接命中缓存了。
通过这种方式,绝大多数读请求在缓存层就被拦截掉了,真正打到数据库的请求大幅减少,系统的响应速度和承载能力都得到了质的提升。
7.3 为什么选 Redis 而不是 Memcached
缓存中间件的代表有两个:Memcached 和 Redis。Memcached 出现更早,但只支持简单的字符串类型。Redis 则支持字符串、哈希、列表、集合、有序集合等多种数据结构,还提供持久化、主从复制、高可用等能力。正因如此,Redis 逐渐成为分布式缓存的首选,Memcached 的使用场景越来越少。
7.4 引入缓存带来的新问题
引入 Redis 之后,随之而来也出现了一批新问题,后续文章会专门讲,这里先混个眼熟:
缓存穿透:请求的数据在缓存和数据库里都不存在(比如攻击者恶意查询不存在的 key),每次都绕过缓存直接打到数据库,缓存形同虚设。
缓存雪崩:大量缓存 key 在同一时刻集中过期,瞬间所有请求都涌向数据库,数据库可能被打垮。
缓存击穿:某个极热的 key 刚好过期,大量并发请求同时穿透缓存涌向数据库,造成数据库瞬间压力飙升。
这些都是使用缓存时必须面对和解决的经典问题。
八、阶段六:数据库分库分表
8.1 数据量继续爆炸
Redis 缓存挡住了大量读请求,但写请求和数据量本身仍在持续增长。当单张表积累到几千万、几亿行数据时,哪怕有索引,查询性能也会急剧下降,磁盘 IO 成为新的瓶颈。
解决方案是分库分表:把数据按照某种规则拆分到多个库、多张表中,让每张表的数据量都保持在可控范围内。
以电商为例:
- 评论表按商品 ID 取模,哈希到不同的表中存储。
- 支付记录按小时建表,同一小时内的记录存在一张表里。
- 用户数据、商品数据、交易数据分别存在各自独立的数据库(垂直分库)。
text
[应用服务器集群]
│
├──→ [用户库(主库 + 从库)]
├──→ [商品库(主库 + 从库)]
└──→ [交易库(主库 + 从库)]
这个阶段用到的技术:MyCat、TiDB、Greenplum 等分布式数据库或中间件。
8.2 分库分表的代价
分库分表显著增加了运维难度。跨库的联表查询变得非常复杂,事务保证也更加困难,对 DBA 的要求很高。这也是为什么很多中小型系统能不分库分表就尽量不分,能用缓存扛住就先用缓存扛。
九、阶段七:微服务架构
9.1 业务拆分的必要性
随着团队规模扩大、业务越来越复杂,把所有业务塞在一个应用里开始带来新的麻烦:
- 任何一个模块出 bug,整个应用可能一起挂掉。
- 不同团队同时修改同一份代码,互相踩脚。
- 某个模块需要扩容,整个应用必须一起扩,资源浪费严重。
解决方案是微服务拆分:把用户系统、商品系统、交易系统拆成独立部署的微服务,每个团队负责自己的服务,独立迭代、独立部署、独立扩容。
text
[电商系统入口 / API 网关]
│
┌──────────────┼──────────────┐
↓ ↓ ↓
[用户子系统] [商品子系统] [交易子系统]
应用集群 应用集群 应用集群
Redis缓存 Redis缓存 Redis缓存
独立数据库 独立数据库 独立数据库
[公共服务:安全中心、监控预警中心等]
9.2 Redis 在微服务里的位置
可以看到,在微服务阶段,每个子系统都配备了自己独立的 Redis 缓存。Redis 不再是整个系统的一个公共组件,而是渗透到了每一个微服务内部,成为不可或缺的基础设施。
十、总结
回顾整个演进过程,我们可以清晰地看到每个阶段遇到的瓶颈和对应的解决方案:
| 阶段 | 核心瓶颈 | 解决方案 |
|---|---|---|
| 单机架构 | 应用和数据库抢占同一台机器资源 | 应用与数据库分离部署 |
| 应用数据分离 | 单台应用服务器触顶 | 负载均衡 + 应用集群水平扩展 |
| 应用集群 | 单台数据库成为瓶颈 | 读写分离,主从复制 |
| 读写分离 | 热点数据反复查库,性能浪费 | 引入 Redis 缓存层 ✅ |
| 引入缓存 | 单库数据量过大,查询变慢 | 分库分表 |
| 分库分表 | 业务耦合、团队协作困难 | 微服务拆分 |
Redis 在读写分离之后的阶段登场,核心使命是把热数据放进内存,让绝大多数读请求在到达数据库之前就被拦截,既大幅提升了响应速度,又保护了数据库不被压垮。
但 Redis 能做的远不止缓存。排行榜、计数器、消息队列、分布式锁......这些场景我们都会在后续的文章中一一展开。现在最重要的是记住一件事:Redis 不是凭空出现的,它是被真实的业务压力一步步逼出来的。 理解了这个背景,后面学到的每一个 Redis 特性都会有更清晰的落地感。
下一篇预告:正式认识 Redis ------ 它究竟有哪些特性,能做什么,不能做什么,以及从 2.6 到 7.0 它经历了怎样的版本演进。