【Redis篇】为什么需要 Redis:从单机到分布式的架构演进之路
文章目录为什么需要 Redis从单机到分布式的架构演进之路一、前言二、基本概念2.1 应用、模块与组件2.2 分布式与集群2.3 主Master与从Slave2.4 中间件2.5 衡量系统好坏的三个指标三、阶段一单机架构3.1 最开始的样子3.2 单机架构的问题四、阶段二应用与数据分离4.1 最低成本的第一步拆分4.2 新的瓶颈单台应用服务器撑不住了五、阶段三应用集群 负载均衡5.1 多台应用服务器协同工作5.2 常见的负载均衡调度算法5.3 新的瓶颈数据库撑不住了六、阶段四读写分离6.1 主从分离的核心思路6.2 为什么读写分离有效6.3 新的瓶颈热点数据反复查数据库七、阶段五引入缓存 —— Redis 登场7.1 冷热数据的区别7.2 引入缓存后的架构7.3 为什么选 Redis 而不是 Memcached7.4 引入缓存带来的新问题八、阶段六数据库分库分表8.1 数据量继续爆炸8.2 分库分表的代价九、阶段七微服务架构9.1 业务拆分的必要性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 TimeRT用户发出请求到收到结果的时间。原则上越短越好但很多场景下要根据实际情况权衡。吞吐量 / 并发Throughput / Concurrent吞吐量是单位时间内成功处理的请求数并发是系统同一时刻能承载的最高请求量。我们平时说的高并发就是追求这个指标。好概念铺垫完毕下面进入正题。三、阶段一单机架构3.1 最开始的样子任何系统在刚起步时都是单机架构。假设我们要做一个电商网站初期团队小、预算紧、用户少整个系统就跑在一台服务器上用户 ↓ [单台服务器] ├── 应用服务处理业务逻辑用户、商品、交易 └── 数据库服务存储所有数据 ├── 用户表 ├── 商品表 └── 交易表用户在浏览器输入www.shop.comDNS 把域名解析成 IP 地址浏览器访问那台服务器服务器处理完业务逻辑后从数据库读写数据最后把结果返回给用户。这个阶段技术栈很简单一个 Web 服务器软件Tomcat、Nginx 等加一个数据库MySQL 等搭起来就能跑。大多数同学在学校做的课程设计、毕业设计基本都是这个阶段的架构。3.2 单机架构的问题单机的核心问题是应用和数据库共享同一台机器的 CPU、内存、磁盘。随着访问量增加两者会互相抢占资源。应用逻辑跑得慢数据库也跑得慢整体性能很快触顶迟早需要升级。四、阶段二应用与数据分离4.1 最低成本的第一步拆分网站上线后逐渐积累了一批稳定用户访问量开始上升单机开始撑不住。此时预算依然有限最低成本的优化方案是把应用服务和数据库服务分开分别部署到两台服务器上。用户 ↓ [应用服务器] [存储服务器] 应用服务 ─── 网络 ─── 数据库服务 处理业务逻辑 ├── 用户表 ├── 商品表 └── 交易表两台机器各司其职不再互相抢资源系统的承载能力得到明显提升而且改动成本很低只需要把数据库迁移到另一台机器上即可。4.2 新的瓶颈单台应用服务器撑不住了随着业务继续增长出现了爆款商品流量暴增单台应用服务器又触顶了。这时候摆在面前的有两条路垂直扩展Scale Up花大钱换一台性能更强的服务器。优点是不需要改代码缺点是硬件性能和价格不是线性关系——性能翻倍价格可能翻四倍而且硬件性能本身存在物理上限。水平扩展Scale Out多加几台同等规格的普通服务器把流量分摊开。优点是成本可控扩展上限高缺点是系统复杂度增加需要解决流量怎么分的问题。长期来看水平扩展才是正确的路。五、阶段三应用集群 负载均衡5.1 多台应用服务器协同工作水平扩展之后有了多台应用服务器但随之而来一个问题用户的请求该发给哪台这就需要一个负载均衡器来统一接收请求再按照一定的策略分发出去。用户 ↓ [负载均衡器] ├── [应用服务器 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 主从分离的核心思路解决数据库瓶颈的方案是读写分离保留一台主库负责所有的写操作增删改其余的从库数据全部从主库同步过来只负责响应读请求。用户 ↓ [负载均衡器] ├── [应用服务器 1] ├── [应用服务器 2] └── [应用服务器 3] │ ├── 写请求 ──→ [主数据库] │ ↓ 数据同步 └── 读请求 ──→ [从数据库 1] ──→ [从数据库 2] ──→ [从数据库 3]6.2 为什么读写分离有效绝大多数互联网系统的读写请求比例都是严重不对称的。以电商为例可能 100 次操作里有 95 次是读浏览商品、查订单只有 5 次是写。把读压力分散到多台从库主库只承担写入整体压力就大幅降低了。当然这个方案不是没有代价的主库到从库的数据同步存在一定的时间延迟也就是说从库的数据会短暂地落后于主库。大部分场景下这个延迟可以接受但对于强一致性要求高的场景比如支付结果查询就需要额外处理。这个阶段用到的技术MyCat、TDDL、Amoeba 等数据库中间件负责自动将读写请求路由到对应的库。6.3 新的瓶颈热点数据反复查数据库读写分离之后系统又撑了一段时间但新的问题浮现出来有些数据被反复查询每次都要走数据库哪怕数据根本没有变化。比如电商首页的爆款商品信息每秒可能被查询几十万次而这些数据几个小时都不会变一次。每次查询都打到数据库既慢、又浪费还在白白消耗数据库的连接资源。这个问题靠继续加从库已经解决不了了需要引入新的解决思路。七、阶段五引入缓存 —— Redis 登场7.1 冷热数据的区别这时候我们意识到数据可以按照访问频率分成两类热数据被频繁读取、但不常变化的数据。比如商品基本信息、首页推荐列表、用户 Session。冷数据不常被访问的数据。比如用户几年前的历史订单、归档日志。对于热数据每次都去数据库查完全没必要。把它放进内存缓存起来直接从内存读速度比磁盘快几十倍乃至上百倍同时数据库的压力也大幅降低。7.2 引入缓存后的架构用户 ↓ [负载均衡器] ├── [应用服务器 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 取模哈希到不同的表中存储。支付记录按小时建表同一小时内的记录存在一张表里。用户数据、商品数据、交易数据分别存在各自独立的数据库垂直分库。[应用服务器集群] │ ├──→ [用户库主库 从库] ├──→ [商品库主库 从库] └──→ [交易库主库 从库]这个阶段用到的技术MyCat、TiDB、Greenplum 等分布式数据库或中间件。8.2 分库分表的代价分库分表显著增加了运维难度。跨库的联表查询变得非常复杂事务保证也更加困难对 DBA 的要求很高。这也是为什么很多中小型系统能不分库分表就尽量不分能用缓存扛住就先用缓存扛。九、阶段七微服务架构9.1 业务拆分的必要性随着团队规模扩大、业务越来越复杂把所有业务塞在一个应用里开始带来新的麻烦任何一个模块出 bug整个应用可能一起挂掉。不同团队同时修改同一份代码互相踩脚。某个模块需要扩容整个应用必须一起扩资源浪费严重。解决方案是微服务拆分把用户系统、商品系统、交易系统拆成独立部署的微服务每个团队负责自己的服务独立迭代、独立部署、独立扩容。[电商系统入口 / API 网关] │ ┌──────────────┼──────────────┐ ↓ ↓ ↓ [用户子系统] [商品子系统] [交易子系统] 应用集群 应用集群 应用集群 Redis缓存 Redis缓存 Redis缓存 独立数据库 独立数据库 独立数据库 [公共服务安全中心、监控预警中心等]9.2 Redis 在微服务里的位置可以看到在微服务阶段每个子系统都配备了自己独立的 Redis 缓存。Redis 不再是整个系统的一个公共组件而是渗透到了每一个微服务内部成为不可或缺的基础设施。十、总结回顾整个演进过程我们可以清晰地看到每个阶段遇到的瓶颈和对应的解决方案阶段核心瓶颈解决方案单机架构应用和数据库抢占同一台机器资源应用与数据库分离部署应用数据分离单台应用服务器触顶负载均衡 应用集群水平扩展应用集群单台数据库成为瓶颈读写分离主从复制读写分离热点数据反复查库性能浪费引入 Redis 缓存层✅引入缓存单库数据量过大查询变慢分库分表分库分表业务耦合、团队协作困难微服务拆分Redis 在读写分离之后的阶段登场核心使命是把热数据放进内存让绝大多数读请求在到达数据库之前就被拦截既大幅提升了响应速度又保护了数据库不被压垮。但 Redis 能做的远不止缓存。排行榜、计数器、消息队列、分布式锁……这些场景我们都会在后续的文章中一一展开。现在最重要的是记住一件事Redis 不是凭空出现的它是被真实的业务压力一步步逼出来的。理解了这个背景后面学到的每一个 Redis 特性都会有更清晰的落地感。下一篇预告正式认识 Redis —— 它究竟有哪些特性能做什么不能做什么以及从 2.6 到 7.0 它经历了怎样的版本演进。