
文章目录第一章 认识 Redis1. Redis 是什么2. 为什么 Redis 要把数据放在内存中3. 既然变量也存在内存里为什么还需要 Redis4. Redis 解决了不同进程之间的数据共享问题5. Redis 和 MySQL 应该怎么选择6. 二八原则与热点数据7. Redis MySQL 也会带来新的问题8. 没有银弹第二章 分布式系统1. 为什么需要分布式系统2. 从最简单的单机架构开始3. 第一次拆分应用服务与数据库分离4. 应用服务器不够用了引入应用集群4.1 负载均衡如何分配请求5. 应用服务器解决了数据库又成为瓶颈6. 主从架构与读写分离7. 数据库还是太忙引入缓存8. 数据越来越多分库分表9. 应用越来越大业务拆分与微服务10. 分布式系统中的常见概念10.1 应用 Application / 系统 System10.2 模块 Module / 组件 Component10.3 分布式 Distributed10.4 集群 Cluster10.5 主 Master / 从 Slave10.6 中间件 Middleware11. 衡量一个系统的几个重要指标11.1 可用性 Availability11.2 响应时长 Response Time11.3 吞吐量 Throughput 与并发 Concurrent12. 分布式架构的整体演进第一章 认识 Redis1. Redis 是什么Redis 官方对自己的描述是The open source, in-memory data store used by millions of developers as a database, cache, streaming engine, and message broker.简单来说Redis 是一个开源的内存数据存储系统可以作为数据库、缓存、流式数据处理引擎以及消息中间件使用。这里最重要的关键词是in-memory内存存储。Redis 的数据主要保存在内存中因此数据读写速度非常快这也是 Redis 高性能的重要原因。Redis 最初一个很典型的用途就是作为消息中间件。在分布式系统中经常存在生产者和消费者生产者产生数据消费者处理数据而 Redis 可以作为两者之间传递消息的中间层。生产者 → Redis → 消费者不过 Redis 并不是专门为消息队列设计的产品。对于更加复杂、更加专业的消息场景通常会使用专门的消息中间件。Redis 可以承担消息传递工作但这只是它众多能力中的一种。2. 为什么 Redis 要把数据放在内存中传统数据库例如 MySQL数据最终主要存储在磁盘中。磁盘容量大、成本低但和内存相比访问速度更慢。Redis 则把数据主要放在内存中MySQL磁盘存储为主 Redis内存存储为主因此在很多简单的数据读写场景中Redis 的速度非常快。但内存也存在明显的缺点容量有限而且成本高。所以虽然 Redis 也可以直接作为数据库使用但如果需要保存非常大量的数据把所有数据都放在内存中往往并不划算。也就是说Redis 的特点非常鲜明速度快但存储空间相对昂贵、有限。3. 既然变量也存在内存里为什么还需要 Redis普通程序中的变量本身同样存储在内存中。例如intcount10;count这个变量就在当前程序的内存中。从这个角度看似乎直接使用变量就可以了为什么还需要 Redis关键区别并不只是“是不是使用内存”而是这些数据能不能被其他进程使用。如果只是一个单机程序并且数据只需要当前程序自己使用那么直接使用程序中的变量往往更加简单没有必要专门引入 Redis。Redis 真正能够发挥威力的地方是分布式系统。4. Redis 解决了不同进程之间的数据共享问题不同进程之间具有隔离性。例如有两个程序进程 A 进程 B 变量 data 10 无法直接访问 A 的变量进程 A 中定义的变量属于进程 A 自己的内存空间进程 B 不能直接拿来使用。而分布式系统中一个系统往往由多个进程组成甚至这些进程还运行在不同的主机上。因此就会遇到一个非常重要的问题不同进程之间如何共享数据进程之间进行通信一种最常见的方式就是通过网络。Redis 本身就是一个独立运行、可以通过网络访问的服务。一个进程可以把数据存入 Redis其他进程再通过网络访问 Redis从而获得这份数据。进程 A │ │ 写入数据 ▼ Redis ▲ │ 读取数据 │ 进程 B甚至进程 A 和进程 B 不在同一台主机上只要它们都能够连接 Redis就可以共享 Redis 中的数据。因此可以把 Redis 简单理解成Redis 把原本只能属于某一个进程的内存数据变成了可以通过网络被多个进程共同访问的数据。这也是 Redis 在分布式系统中非常重要的原因。5. Redis 和 MySQL 应该怎么选择Redis 快但是内存空间有限MySQL 相比之下速度没有 Redis 这么快但更加适合保存大量数据。因此实际开发中并不一定要在 Redis 和 MySQL 之间二选一更典型的方案是Redis 和 MySQL 配合使用。MySQL 保存完整的数据Redis 保存那些经常被访问的数据。热点数据 │ ▼ Redis 完整数据 │ ▼ MySQL这样既可以利用 Redis 的速度也可以利用 MySQL 更大的存储能力。6. 二八原则与热点数据很多系统中的数据访问并不是平均的而是少部分数据会被反复访问。这可以用常见的二八原则来理解大约 20% 的热点数据可能满足大约 80% 的访问需求。这里的20%和80%并不是固定不变的数字重点在于表达少量热点数据承担了大量访问请求。既然如此就没有必要把数据库中的所有数据全部放进 Redis。可以只把经常访问的热点数据放进 Redis大量完整数据 → MySQL 少量热点数据 → Redis访问热点数据时直接读取 Redis就可以利用 Redis 的高性能其他数据仍然保存在 MySQL 中。这也是 Redis 最典型的使用方式之一。7. Redis MySQL 也会带来新的问题Redis 和 MySQL 配合使用虽然可以提高访问速度但系统也会变得更加复杂。原本只有一份数据程序 → MySQL现在变成了两份Redis / 程序 \ MySQL这时就会产生一个新的问题如果数据发生修改Redis 和 MySQL 中的数据如何保持同步例如 MySQL 中的数据已经修改但是 Redis 中仍然保存旧数据那么程序从 Redis 中读取到的就是错误的数据。因此引入 Redis 之后虽然提高了性能却同时引入了 Redis 与 MySQL 之间的数据同步问题。这说明一个很重要的道理架构越复杂能够解决的问题越多但同时也会产生新的问题。Redis 并不是用了之后系统就一定更好。如果业务本身非常简单为了追求性能而盲目增加 Redis反而可能让系统变得更加复杂。8. 没有银弹软件开发中有一句非常经典的话没有银弹。“银弹”在传说中是用来击败狼人的特殊武器后来被用来形容一种能够轻松解决所有问题的万能方案。但软件开发中并不存在真正的万能方案。Redis 很快但它需要内存Redis 可以提高系统性能但它也会增加系统复杂度Redis 和 MySQL 配合能够减轻数据库压力但同时又会产生数据同步问题。所以学习 Redis不能简单理解成Redis 快所以哪里都应该用 Redis而应该理解成Redis 快 ↓ 适合解决特定的性能问题 ↓ 但也会带来新的复杂度 ↓ 需要根据实际场景选择而 Redis 真正能够体现价值的场景通常都离不开一个重要背景——分布式系统。第二章 分布式系统1. 为什么需要分布式系统一个系统刚开始时通常并不需要复杂的分布式架构。用户量少、访问压力小一台服务器往往就能够同时承担应用程序和数据库的工作。但一台服务器的硬件资源始终是有限的例如CPU 内存 磁盘 网络服务器每处理一个请求都需要消耗这些硬件资源。当用户越来越多、请求越来越密集时单台服务器最终一定会接近自己的性能极限。面对这个问题最直接的办法有两个。第一种是升级硬件例如更换更强的 CPU、更大的内存和更快的磁盘这种方式称为Scale Up纵向扩展 / 垂直扩展。它的优点是简单原来的程序通常不需要进行太大的修改缺点也很明显硬件性能存在上限而且越高端的硬件价格通常越昂贵。第二种是增加服务器数量让多台机器共同承担原本一台机器的工作这种方式称为Scale Out横向扩展 / 水平扩展。它能够获得更大的扩展空间但随着服务器越来越多不同机器之间需要通信和协调系统也会变得更加复杂。因此可以先建立一个非常重要的认识所谓分布式本质上就是当一台机器无法满足需求时引入更多机器共同完成工作。2. 从最简单的单机架构开始假设现在要开发一个简单的电商网站其中包含用户、商品、交易三个主要业务。在系统刚刚上线的时候用户量很少没有必要一开始就搭建复杂的架构因此可以把应用程序和数据库全部部署在同一台服务器上。用户在浏览器中输入网站地址后首先通过 DNS 将域名解析成服务器的 IP 地址然后浏览器向这台服务器发送 HTTP 请求。应用程序负责处理用户、商品和交易等业务逻辑需要访问数据时再调用 MySQL 等数据库。这种架构最大的优点就是简单。开发简单、部署简单、维护也简单在业务量还很小的时候完全够用所以很多系统都是从单机架构开始的。但随着用户越来越多应用程序和数据库都在争抢同一台服务器的 CPU、内存、磁盘和网络资源单机最终还是会达到性能极限。3. 第一次拆分应用服务与数据库分离当单机开始出现压力时不一定需要立刻增加大量服务器一个很自然的做法就是先把应用服务和数据库服务拆开。原本应用程序 数据库 ↓ 一台服务器拆分之后应用程序放到应用服务器数据库放到独立的存储服务器两台服务器通过网络通信。这样做有两个明显好处。第一应用程序和数据库不再争抢同一台机器的硬件资源。应用程序主要负责业务逻辑通常更加依赖 CPU 和内存数据库需要保存大量数据更加关注存储容量和磁盘读写能力因此两种服务器可以根据自己的特点配置不同的硬件。第二系统已经可以使用两台机器的硬件资源而不是所有工作都挤在一台机器中。此时应用程序访问数据库的方式也发生了变化。原来它们在同一台服务器中现在应用服务器需要通过网络访问数据库服务器。这其实已经具有了分布式系统最基本的特点原本位于同一台机器中的不同部分被部署到不同机器并通过网络共同完成工作。4. 应用服务器不够用了引入应用集群系统继续发展以后数据库虽然已经独立出去但应用服务器仍然只有一台。当用户请求越来越多时单台应用服务器同样会达到极限。这时可以继续进行水平扩展多台应用服务器共同处理业务就形成了应用服务器集群。但是服务器增加以后马上就会出现一个新的问题用户的请求到底应该交给哪一台应用服务器因此需要在用户和应用服务器之间增加一个新的组件——负载均衡Load Balance。┌──► 应用服务器 A 用户 ──► 负载均衡 ──┤ └──► 应用服务器 B负载均衡本质上就是一个“任务分配器”。用户的请求先到负载均衡再由它决定把请求发送给哪一台应用服务器。这样多台服务器的计算能力才能真正被利用起来。4.1 负载均衡如何分配请求最简单的方式是Round-Robin轮询。例如存在 A、B、C 三台服务器第一个请求交给 A第二个交给 B第三个交给 C第四个再重新交给 A如此循环。如果不同服务器的性能不同还可以使用Weight Round-Robin加权轮询。性能更强的服务器设置更高权重让它处理更多请求性能较弱的服务器处理较少请求也就是“能者多劳”。还有一种方式是根据用户特征计算哈希值例如根据用户 IP 地址进行计算再根据结果选择服务器。这样可以让同一个用户的请求尽量稳定地进入同一台服务器。常见的负载均衡软件包括 Nginx、HAProxy、LVS、F5 等。这里真正需要理解的是应用服务器变多以后必须有人负责把请求分发给这些服务器这就是负载均衡存在的原因。5. 应用服务器解决了数据库又成为瓶颈应用服务器可以从一台扩展到两台、四台甚至更多因此应用层能够同时处理越来越多的请求。但无论有多少应用服务器它们最终都需要访问数据库。于是系统会出现新的问题应用服务器越来越多 ↓ 数据库请求越来越多 ↓ 数据库成为新的瓶颈数据库和应用服务器不同不能简单地复制几台以后随便使用因为数据库中保存的是系统的数据。假设用户账户中有 1000 元完成一次支付后数据库 A 已经变成 900 元但数据库 B 仍然保存 1000 元那么用户访问不同数据库就可能看到完全不同的结果。这就是数据库非常重要的一个问题——数据一致性。简单来说对于同一份数据不应该因为访问了不同数据库就得到不同的结果。6. 主从架构与读写分离为了解决数据库扩展问题可以引入主从架构。多个数据库节点中保留一个主要数据库作为主库Master其他数据库作为从库Slave主库主要负责数据写入当主库中的数据发生变化之后再把变化同步到从库让从库尽量保持和主库相同的数据。有了主库和从库以后就可以进一步进行读写分离写请求 → 主数据库 读请求 → 从数据库为什么这样可以提高性能因为很多系统中的读取请求远远多于写入请求。例如可能存在 100 次读取却只有 1 次写入。如果所有请求全部交给主库那么主库需要承担全部压力现在把大量读取请求分散给多个从库主库的压力自然就会明显降低。当然主库的数据同步到从库需要一定时间因此主从之间可能短暂出现数据不同步。这里暂时只需要理解读写分离最核心的思想主库负责写从库帮助主库承担大量读取请求。实际项目中还可以使用 MyCat、TDDL、Amoeba、Cobar 等数据库中间件帮助完成读写请求的分离。7. 数据库还是太忙引入缓存即使已经进行了读写分离如果访问量继续增长数据库仍然可能需要处理大量读取请求。这时继续观察业务会发现一个非常重要的特点并不是所有数据都会被同样频繁地访问。例如热门商品、热门页面可能一天被访问几十万次而一些冷门商品很长时间才会被访问一次。因此可以把访问频率很高的数据称为热点数据访问频率较低的数据称为冷数据。对于热点数据没有必要每一次请求都重新查询数据库可以把它们提前保存到缓存中。应用程序读取数据时首先访问缓存。如果缓存中已经存在结果就直接返回不再访问数据库只有缓存中没有需要的数据时才继续查询数据库。这样大量针对热点数据的请求就会在真正到达数据库之前被缓存拦截从而明显降低数据库压力。这正是 Redis 在分布式系统中非常重要的角色Redis 作为分布式缓存保存大量访问频繁的热点数据。这也和前面提到的“二八原则”联系起来少量热点数据往往承担了大量访问请求因此只需要把这些数据缓存起来就能获得非常明显的效果。不过缓存也不是没有代价。例如数据库中的商品价格已经从100修改成80而 Redis 中仍然保存100那么用户读取 Redis 时得到的就是旧数据。于是又产生了一个新的问题Redis 中的数据如何和数据库保持一致除此之外还会继续遇到缓存穿透、缓存击穿、缓存雪崩、热点数据集中失效等问题这些内容会在后面的 Redis 学习中继续展开。8. 数据越来越多分库分表缓存解决的主要是访问压力但如果业务中的数据本身越来越多又会出现另一个问题一个数据库已经放不下这么多数据了怎么办例如最开始所有数据都放在同一个数据库中数据库 ├── 用户表 ├── 商品表 └── 交易表当数据量越来越大时就可以按照业务继续拆分例如用户数据 → 用户库 商品数据 → 商品库 交易数据 → 交易库这种按照业务职责拆分数据库的方式可以理解为垂直分库。拆分之后不同数据库还可以部署到不同的服务器中于是原来只有一台数据库服务器的系统开始逐渐形成多个独立的存储集群。如果某一张表本身仍然非常大还可以继续拆表。例如评论数据可以根据商品 ID 进行 Hash然后根据 Hash 结果决定数据应该进入哪一张表支付记录也可以按照时间划分再根据用户 ID 或记录编号继续拆分。不管具体采用什么规则它背后的思想都非常简单一张表太大就拆成多张表一台数据库服务器压力太大就把数据分散到更多服务器。这样数据库也可以通过增加服务器数量继续扩展。但分库分表以后数据库管理、数据路由、查询和运维都会变得更加复杂因此它同样不是免费的性能提升。9. 应用越来越大业务拆分与微服务前面的拆分主要解决服务器性能和数据库压力但随着系统继续发展还有一个问题会越来越明显——业务本身也会越来越复杂。最开始的一个应用程序中可能同时包含用户 商品 交易业务少的时候这样没有问题但随着功能越来越多、开发人员越来越多一个巨大的应用会越来越难开发和维护。于是可以继续按照业务进行拆分用户服务 商品服务 交易服务不同业务交给不同的开发团队负责每个团队维护自己的服务。不同服务之间不再随意访问对方的数据而是通过 Gateway、消息总线等方式进行通信。进一步发展以后每一个子系统内部甚至都可以拥有自己的应用集群、缓存和存储集群。例如用户系统拥有自己的应用服务器、Redis 和数据库商品系统同样拥有自己的应用服务器、Redis 和数据库。一些多个业务都会使用的功能例如安全管理、监控预警、数据采集等也可以继续抽取成公共服务。这就是微服务逐渐形成的过程。微服务解决的不只是“服务器性能不够”还解决另外一个非常现实的问题当项目越来越大、开发人员越来越多时如何让不同团队更加独立地开发和维护自己的业务。所以系统最终如何拆分本质上还是取决于业务本身。10. 分布式系统中的常见概念经过前面的架构演进再来看分布式系统中的一些常见概念就会简单很多。10.1 应用 Application / 系统 System应用或者系统可以理解为为了完成一整套业务而存在的一个程序或者一组相互配合的程序。例如一个完整的电商系统就是一个应用或系统它共同完成用户、商品、交易等业务。10.2 模块 Module / 组件 Component一个应用内部通常还会继续划分不同功能。例如电商系统 ├── 用户模块 ├── 商品模块 └── 交易模块这些职责清晰、功能相对独立的部分就可以称为模块或者组件。简单来说应用表示一个完整系统模块表示系统内部的某一部分功能。10.3 分布式 Distributed如果一个系统的不同部分被部署在不同的服务器上并通过网络共同完成工作就可以认为它具有分布式的特点。例如服务器 A应用服务 服务器 B数据库服务两台服务器通过网络通信共同完成整个系统的业务。所以分布式可以简单理解为把系统的不同工作分散到不同物理机器上再通过网络协同完成。10.4 集群 Cluster集群强调的是多台服务器为了完成同一个目标共同工作。“集群”强调的是多个节点协同工作不要求每个节点必须执行完全相同的任务。对于应用服务器集群通常是同一任务的多个副本对于存储和计算集群节点可能负责不同的数据或不同的子任务。例如多台应用服务器共同提供 Web 服务可以称为应用服务器集群多台数据库服务器共同提供数据库服务可以称为数据库集群。因此可以简单区分分布式 → 更强调物理上分散到不同服务器 集群 → 更强调多个节点共同完成一个目标实际学习中没有必要过度纠结两者细微的边界。10.5 主 Master / 从 Slave主从是一种常见的集群结构。其中一个节点承担主要职责称为主节点其他节点承担辅助职责称为从节点。例如前面的数据库主从架构主数据库 │ │ 数据同步 ▼ 从数据库主库负责主要的数据修改从库的数据来自主库同步并帮助主库承担部分查询压力。10.6 中间件 Middleware中间件可以简单理解成夹在不同程序之间为它们提供通用能力的软件。例如前面出现过的数据库中间件、缓存、消息队列等都不是具体的用户、商品、交易业务却可以为业务系统提供数据访问、缓存、通信等公共能力。11. 衡量一个系统的几个重要指标11.1 可用性 Availability可用性表示系统在一段时间内能够正常提供服务的程度。可以简单理解为可用性 系统正常运行时间 ÷ 总时间平时常说的“4 个 9”表示系统可用性约为99.99%“5 个 9”则表示约99.999%。可用性越高意味着系统一年中允许出现故障的时间越短。所谓高可用High AvailabilityHA本质上就是希望系统尽可能长时间保持正常服务。11.2 响应时长 Response Time响应时长简称RTResponse Time表示从用户发出请求到系统返回结果所经历的时间。例如一次请求耗时20ms那么这次请求的响应时间就是20ms。一般来说响应时间越短用户感觉系统越快。实际系统中通常还会关注平均响应时间、中位数响应时间以及较慢请求的响应时间。11.3 吞吐量 Throughput 与并发 Concurrent并发表示同一个时刻系统能够同时处理多少请求。吞吐量表示一段时间内系统总共能够处理多少请求。例如一条道路同一时刻最多容纳 2 辆车但一分钟可以通过 20 辆车那么可以简单理解为并发2 吞吐量20 辆 / 分钟服务器也是一样。并发关注的是“同时有多少请求正在处理”吞吐量关注的是“一段时间总共处理了多少请求”。实际系统中并发量往往不容易直接统计因此经常使用很短时间内的吞吐量例如每秒能够处理多少请求来衡量服务器的处理能力。12. 分布式架构的整体演进回头看整个过程系统并不是突然变成一个庞大的分布式系统而是随着业务不断增长一步一步演进出来的这并不是一套必须严格执行的固定流程。实际项目首先遇到什么问题就应该优先解决什么问题。有的系统用户量非常大所以首先需要解决高并发有的系统并发并不高但业务功能特别复杂那么首先要解决的可能是业务拆分。因此架构的演进应该由实际业务和实际瓶颈推动而不是为了使用某种技术而增加系统复杂度。而在整个演进过程中Redis 的位置已经非常清楚Redis 通常位于应用程序和数据库之间作为分布式缓存保存热点数据通过内存的高速访问能力拦截大量数据库查询。这也是学习 Redis 之前必须先理解分布式系统的原因。