从古至今,长江和黄河流域水患不断,远古时期,大禹曾拓宽河道,清除淤沙让流水更加顺
畅;都江堰作为史上最成功的的治水案例之一,用引流将岷江之水分流到多个支流中,以分
担水流压力;三门峡和葛洲坝通过建造水库将水引入水库先存储起来,然后再想办法把水库
中的水缓缓地排出去,以此提高下游的抗洪能力。
数据库--->缓存--->CDN--->消息队列--->微服务
而我们在应对高并发大流量时也会采用类似“抵御洪水”的方案,归纳起来共有三种方法。
Scale-out(横向扩展):分而治之是一种常见的高并发系统设计方法,采用分布式部署
的方式把流量分流开,让每个服务器都承担一部分并发和流量。(Scale-up,通过购买性能更好的硬件来提升系统的并发处理能力,比方说目前系统 4 核4G 每秒可以处理 200 次请求,那么如果要处理 400 次请求呢?很简单,我们把机器的硬件提升到 8 核 8G(硬件资源的提升可能不是线性的,这里仅为参考)。Scale-out,则是另外一个思路,它通过将多个低性能的机器组成一个分布式集群来共同抵御高并发流量的冲击。沿用刚刚的例子,我们可以使用两台 4 核 4G 的机器来处理那400 次请求。)
缓存:使用缓存来提高系统的性能,就好比用“拓宽河道”的方式抵抗高并发大流量的冲
击。(普通磁盘的寻道时间是 10ms 左右,而相比于磁盘寻道花费的时间,CPU 执行指令和内存
寻址的时间都在是 ns(纳秒)级别,从千兆网卡上读取数据的时间是在μs(微秒)级别。
所以在整个计算机体系中,磁盘是最慢的一环,甚至比其它的组件要慢几个数量级。因此,
我们通常使用以内存作为存储介质的缓存,以此提升性能。)
异步:在某些场景下,未处理完成之前,我们可以让请求先返回,在数据准备好之后再通
知请求方,这样可以在单位时间内处理更多的请求。(异步调用在大规模高并发系统中被大量使用,比如我们熟知的 12306 网站。当我们订票时,页面会显示系统正在排队,这个提示就代表着系统在异步处理我们的订票请求。在12306 系统中查询余票、下单和更改余票状态都是比较耗时的操作,可能涉及多个内部系统的互相调用,如果是同步调用就会像 12306 刚刚上线时那样,高峰期永远不可能下单成功。而采用异步的方式,后端处理时会把请求丢到消息队列中,同时快速响应用户,告诉用户我们正在排队处理,然后释放出资源来处理更多的请求。订票请求处理完之后,再通知用户订票成功或者失败。处理逻辑后移到异步处理程序中,Web 服务的压力小了,资源占用的少了,自然就能接收更多的用户订票请求,系统承受高并发的能力也就提升了。)
高并发系统的演进应该是循序渐进,以解决系统中存在的问题为目的和驱动力的。
高并发系统设计的三大目标:高性能、高可用、可扩展
高可用系统设计的思路
- 系统设计
未雨绸缪才能决胜千里。我们在做系统设计的时候,要把发生故障作为一个重要的考虑点,
预先考虑如何自动化地发现故障,发生故障之后要如何解决。当然了,除了要有未雨绸缪的
思维之外,我们还需要掌握一些具体的优化方法,比如failover(故障转移)、超时控制以
及降级和限流(使用最广泛的故障检测机制是“心跳”。你可以在客户端上定期地向主节点发送心跳包,也
可以从备份节点上定期发送心跳包。当一段时间内未收到心跳包,就可以认为主节点已经发
生故障,可以触发选主的操作。
选主的结果需要在多个备份节点上达成一致,所以会使用某一种分布式一致性算法,比方说
Paxos,Raft。) - 系统运维
我们可以从灰度发布、故障演练两个方面来考虑如何提升系统的可用性(故障演练和时下比较流行的“混沌工程”的思路如出一辙,作为混沌工程的鼻祖,Netfix 在 2010 年推出的“Chaos Monkey”工具就是故障演练绝佳的工具。它通过
在线上系统上随机地关闭线上节点来模拟故障,让工程师可以了解,在出现此类故障时会有什么样的影响。)
部署方式遵照最简单的三层部署架构,负载均衡负责请求的分发,应用服务器负责业务逻辑的处理,数据库负责数据的存储落地。
数据库篇
存储层
存储拆分首先考虑的维度是业务维度。
例如简单的社区系统拆分之后,这个简单的社区系统就有了用户库、内容库、评论库、点赞库和关系库。
用连接池预先建立数据库连接
数据库优化方案(一):查询请求增加时,如何做主从分离?
主从读写分离
(它其实是个流量分离的问题,就好比道路交通管制一样,一个四车道的大马路划出三个车道
给领导外宾通过,另外一个车道给我们使用,优先保证领导先行,就是这个道理。)
在主从读写分离机制中,我们将一个数据库的数据拷贝为一份或者多份,并且写入
到其它的数据库服务器中,原始的数据库我们称为主库,主要负责数据的写入,拷贝的目标
数据库称为从库,主要负责支持数据查询。可以看到,主从读写分离有两个技术上的关键点:
- 一个是数据的拷贝,我们称为主从复制;
- 在主从分离的情况下,我们如何屏蔽主从分离带来的访问数据库方式的变化,让开发同学像是在使用单一数据库一样。
- 主从复制
MySQL 的主从复制是依赖于 binlog 的,也就是记录 MySQL 上的所有变化并以二进制形式保存在磁盘上二进制日志文件。主从复制就是将 binlog 中的数据从主库传输到从库上,一般这个过程是异步的,即主库上的操作不会等待 binlog 同步的完成。
当然,主从复制也有一些缺陷,除了带来了部署上的复杂度,还有就是会带来一定的主从同
步的延迟,这种延迟有时候会对业务产生一定的影响,我举个例子你就明白了。(第一种方案是数据的冗余。你可以在发送消息队列时不仅仅发送微博 ID,而是发送队列处理机需要的所有微博信息,借此避免从数据库中重新查询数据。
第二种方案是使用缓存。我可以在同步写数据库的同时,也把微博的数据写入到Memcached 缓存里面,这样队列处理机在获取微博信息的时候会优先查询缓存,这样也可以保证数据的一致性。最后一种方案是查询主库。我可以在队列处理机中不查询从库而改为查询主库。不过,这种方式使用起来要慎重,要明确查询的量级不会很大,是在主库的可承受范围之内,否则会对主库造成比较大的压力。) - 如何访问数据库
为了降低实现的复杂度,业界涌现了很多数据库中间件来解决数据库的访问问题,这些中间件可以分为两类。
第一类以淘宝的 TDDL( Taobao Distributed Data Layer)为代表,以代码形式内嵌运行
在应用程序内部。
另一类是单独部署的代理层方案,这一类方案代表比较多,如早期阿里巴巴开源的Cobar,基于 Cobar 开发出来的 Mycat,360 开源的 Atlas,美团开源的基于 Atlas 开发的 DBProxy 等等。 - 主从读写分离以及部署一主多从可以解决突发的数据库读流量,是一种数据库横向扩展
的方法; - 读写分离后,主从的延迟是一个关键的监控指标,可能会造成写入数据之后立刻读的时
候读取不到的情况; - 业界有很多的方案可以屏蔽主从分离之后数据库访问的细节,让开发人员像是访问单一
数据库一样,包括有像 TDDL、Sharding-JDBC 这样的嵌入应用内部的方案,也有像
Mycat 这样的独立部署的代理方案。
在使用主从复制这个技术点时,你一般会考虑两个问题: - 主从的一致性和写入性能的权衡,如果你要保证所有从节点都写入成功,那么写入性能
一定会受影响;如果你只写入主节点就返回成功,那么从节点就有可能出现数据同步失败的
情况,从而造成主从不一致,而在互联网的项目中,我们一般会优先考虑性能而不是数据的
强一致性。 - 主从的延迟问题,很多诡异的读取不到数据的问题都可能会和它有关,如果你遇到这类
问题不妨先看看主从延迟的数据。
我们采用的很多组件都会使用到这个技术,比如,Redis 也是通过主从复制实现读写分离;
Elasticsearch 中存储的索引分片也可以被复制到多个节点中;写入到 HDFS 中文件也会被
复制到多个 DataNode 中。只是不同的组件对于复制的一致性、延迟要求不同,采用的方
案也不同。但是这种设计的思想是通用的,是你需要了解的,这样你在学习其他存储组件的
时候就能够触类旁通了。
数据库优化方案(二):写入数据量增加时,如何实现分库分表?
如何对数据库做垂直拆分
分库分表是一种常见的将数据分片的方式,它的基本思想是依照某一种策略将数据尽量平均
的分配到多个数据库节点或者多个表中。
不同于主从复制时数据是全量地被拷贝到多个节点,分库分表后,每个节点只保存部分的数
据,这样可以有效地减少单个数据库节点和单个数据表中存储的数据量,在解决了数据存储
瓶颈的同时也能有效的提升数据查询的性能。同时,因为数据被分配到多个数据库节点上,
那么数据的写入请求也从请求单一主库变成了请求多个数据分片节点,在一定程度上也会提
升并发写入的性能。
数据库分库分表的方式有两种:
一种是垂直拆分,另一种是水平拆分。这两种方式,在我看来,掌握拆分方式是关键,理解拆分原理是内核。
垂直拆分,顾名思义就是对数据库竖着拆分,也就是将数据库的表拆分到多个不同的数据库中。
垂直拆分的原则一般是按照业务类型来拆分,核心思想是专库专用,将业务耦合度比较高的
表拆分到单独的库中。
数据库水平拆分
和垂直拆分的关注点不同,垂直拆分的关注点在于业务相关性,而水平拆分指的是将单一数
据表按照某一种规则拆分到多个数据库和多个数据表中,关注点在数据的特点。
- 按照某一个字段的哈希值做拆分,这种拆分规则比较适用于实体表,比如说用户表,内
容表,我们一般按照这些实体表的 ID 字段来拆分。比如说我们想把用户表拆分成 16 个
库,64 张表,那么可以先对用户 ID 做哈希,哈希的目的是将 ID 尽量打散,然后再对 16
取余,这样就得到了分库后的索引值;对 64 取余,就得到了分表后的索引值。 - 另一种比较常用的是按照某一个字段的区间来拆分,比较常用的是时间字段。你知道在
内容表里面有“创建时间”的字段,而我们也是按照时间来查看一个人发布的内容。我们可
能会要看昨天的内容,也可能会看一个月前发布的内容,这时就可以按照创建时间的区间来
分库分表,比如说可以把一个月的数据放入一张表中,这样在查询时就可以根据创建时间先
定位数据存储在哪个表里面,再按照查询条件来查询(分库分表引入的一个最大的问题就是引入了分库分表键,也叫做分区键,也就是我们对数据库做分库分表所依据的字段)
如何保证分库分表后ID的全局唯一性?
基于 Snowflake 算法搭建发号器,使用的是变种的 Snowflake 算法来生成业务需要的 ID的,本讲的重点,也是运用它去解决 ID 全局唯一性的问题。
NoSQL:在高并发场景下,数据库和NoSQL如何做到互补
NoSQL 想必你很熟悉,它指的是不同于传统的关系型数据库的其他数据库系统的统称,它
不使用 SQL 作为查询语言,提供优秀的横向扩展能力和读写性能,非常契合互联网项目高
并发大数据的特点。所以一些大厂,比如小米、微博、陌陌都很倾向使用它来作为高并发大
容量的数据存储服务。
使用 NoSQL 提升写入性能
NoSQL 数据库在性能、扩展性上的优势,以及它的一些特殊功能特
性,主要有以下几点:
- 在性能方面,NoSQL 数据库使用一些算法将对磁盘的随机写转换成顺序写,提升了写的
性能; - 在某些场景下,比如全文搜索功能,关系型数据库并不能高效地支持,需要 NoSQL 数
据库的支持; - 在扩展性方面,NoSQL 数据库天生支持分布式,支持数据冗余和数据分片的特性。
缓存篇
缓存:缓存,是一种存储数据的组件,它的作用是让对数据的请求更快地返回。
常见的缓存主要就是静态缓存、分布式缓存和热点本地缓存这三种。
分布式缓存,Memcached、Redis 就是分布式缓存的典型例子。它们性能强劲,通过一些分布式的方案组成集群可以突破单机的限制。
缓存的不足
首先,缓存比较适合于读多写少的业务场景,并且数据最好带有一定的热点属性。
其次,缓存会给整体系统带来复杂度,并且会有数据不一致的风险。
再次,之前提到缓存通常使用内存作为存储介质,但是内存并不是无限的。
最后,缓存会给运维也带来一定的成本
你可能会产生这样的思路:先更新数据库中 ID 为 1 的记录,再更新缓存中 Key 为 1 的数
据,缓存和数据库中的数据不一致,因为变更数据库和变更缓存是两个独立的操作,而我们并没有对操
作做任何的并发控制。那么当两个线程并发更新它们的时候,就会因为写入顺序的不同造成
数据的不一致。我们可以在更新数据时不更新缓存,而是删除缓存中的数据,在读取数据时,发现缓存中没了数据之后,再从数据库中读取数据,更新到缓存中
Cache Aside 策略
其中读策略的步骤是:
从缓存中读取数据;
如果缓存命中,则直接返回数据;
如果缓存不命中,则从数据库中查询数据;
查询到数据后,将数据写入到缓存中,并且返回给用户。
其中读策略的步骤是:
从缓存中读取数据;
如果缓存命中,则直接返回数据;
如果缓存不命中,则从数据库中查询数据;
查询到数据后,将数据写入到缓存中,并且返回给用户。
1.Cache Aside 是我们在使用分布式缓存时最常用的策略,你可以在实际工作中直接拿来使
用。
2.Read/Write Through 和 Write Back 策略需要缓存组件的支持,所以比较适合你在实现
本地缓存组件的时候使用;
3.Write Back 策略是计算机体系结构中的策略,不过写入策略中的只写缓存,异步写入后
端存储的策略倒是有很多的应用场景。
- 缓存数据如何分片
单一的缓存节点受到机器内存、网卡带宽和单节点请求量的限制,不能承担比较高的并发,
因此我们考虑将数据分片,依照分片算法将数据打散到多个不同的节点上,每个节点上存储
部分数据。
这样在某个节点故障的情况下,其他节点也可以提供服务,保证了一定的可用性。这就好比
不要把鸡蛋放在同一个篮子里,这样一旦一个篮子掉在地上,摔碎了,别的篮子里还有没摔
碎的鸡蛋,不至于一个不剩。
一般来讲,分片算法常见的就是 Hash 分片算法和一致性 Hash 分片算法两种。
2.Memcached 的主从机制
Redis 本身支持主从的部署方式,但是 Memcached 并不支持,所以我们今天主要来了解
一下 Memcached 的主从机制是如何在客户端实现的。
在之前的项目中,我就遇到了单个主节点故障导致数据穿透的问题,这时我为每一组
Master 配置一组 Slave,更新数据时主从同步更新。读取时,优先从 Slave 中读数据,如
果读取不到数据就穿透到 Master 读取,并且将数据回种到 Slave 中以保持 Slave 数据的
热度。
主从机制最大的优点就是当某一个 Slave 宕机时,还会有 Master 作为兜底,不会有大量请
求穿透到数据库的情况发生,提升了缓存系统的高可用性。 - 多副本
其实,主从方式已经能够解决大部分场景的问题,但是对于极端流量的场景下,一组 Slave
通常来说并不能完全承担所有流量,Slave 网卡带宽可能成为瓶颈
为了解决这个问题,我们考虑在 Master/Slave 之前增加一层副本层
中间代理层方案
虽然客户端方案已经能解决大部分的问题,但是只能在单一语言系统之间复用
中间代理层的方案就可以解决这个问题。你可以将客户端解决方案的经验移植到代理层
中,通过通用的协议(如 Redis 协议)来实现在其他语言中的复用
业界也有很多中间代理层方案,比如 Facebook 的Mcrouter,Twitter 的
Twemproxy,豌豆荚的Codis。它们的原理基本上可以由一张图来概括
缓存穿透其实是指从缓存中没有查到数据,而不得不从后端系统(比如数据库)中查询的情
况。如何解决缓存穿透呢?一般来说我们会有两种解决方案:回种空值以及使用布隆过滤器。
CDN篇
CDN:静态资源如何加速?
静态资源访问的关键点是就近访问,即北京用户访问北京的数据,杭州用户访问杭州
的数据,这样才可以达到性能的最优。
1.DNS 技术是 CDN 实现中使用的核心技术,可以将用户的请求映射到 CDN 节点上;(比如你的公司的一级域名叫做 example.com,那么你可以给你的图片服务的域名定义为“img.example.com”,然后将这个域名的解析结果的 CNAME 配置到 CDN 提供的域名上,比如 uclound 可能会提供一个域名是“80f21f91.cdn.ucloud.com.cn”这个域名。这样你的电商系统使用的图片地址可以是“http://img.example.com/1.jpg”。因为域名解析过程是分级的,每一级有专门的域名服务器承担解析的职责,所以,域名的解析过程有可能需要跨越公网做多次 DNS 查询,在性能上是比较差的,一个解决的思路是:在 APP 启动时,对需要解析的域名做预先解析,然后把解析的结果缓存到本地的一个 LRU 缓存里面。这样当我们要使用这个域名的时候,只需要从缓存中直接拿到所需要的 IP 地址就好了,如果缓存中不存在才会走整个 DNS 查询的过程。同时,为了避免 DNS 解析结果的变更造成缓存内数据失效,我们可以启动一个定时器,定期地更新缓存中的数据。)
2.DNS 解析结果需要做本地缓存,降低 DNS 解析过程的响应时间;
3.GSLB 可以给用户返回一个离着他更近的节点,加快静态资源的访问速度。(如何找到离用户最近的 CDN 节点,GSLB(Global Server Load Balance,全局负载均衡), 它的含义是对于部署在不同地域的服务器之间做负载均衡,下面可能管理了很多的本地负载均衡组件。它有两方面的作用:一方面,它是一种负载均衡服务器,负载均衡,顾名思义嘛,指的是让流量平均分配使得下面管理的服务器的负载更平均;另一方面,它还需要保证流量流经的服务器与流量源头在地缘上是比较接近的。)
消息队列篇
消息队列:秒杀时如何处理每秒上万次的下单请求?
削去秒杀场景下的峰值写流量
将秒杀请求暂存在消息队列中,然后业务服务器会响应用户“秒杀结果正在计算中”,释放了系统资源之后再处理其它用户的请求。
如何保证消息仅仅被消费一次?
比如说,我们会引入 Elasticsearch 来解决商品和店铺搜索的问
题,也会引入审核系统,来对售卖的商品、用户的评论做自动的和人工的审核,你会越来越
多地使用消息队列与外部系统解耦合,以及提升系统性能
如果要保证消息只被消费一次,首先就要保证消息不会丢失
消息的丢失可以通过生产端的重试、消息队列配置集群模式,以及消费端合理处理消费
进度三个方式来解决。
为了解决消息的丢失通常会造成性能上的问题以及消息的重复问题。
通过保证消息处理的幂等性可以解决消息的重复问题。
如何降低消息队列系统中消息的延迟?
,Kafka 提供了工具叫做“kafka-consumer-groups.sh”
Kafka 通过 JMX
我们可以使用消息队列提供的工具,或者通过发送监控消息的方式,来监控消息的延迟
情况;
横向扩展消费者是提升消费处理能力的重要方式;
选择高性能的数据存储方式,配合零拷贝技术,可以提升消息的消费性能。
一体化架构的痛点
在电商项目刚刚启动的时候,你只是希望能够尽量快地将项目搭建起来,方便将产品更早地
投放市场,快速完成验证
首先,在技术层面上,数据库连接数可能成为系统的瓶颈。
在第 7 讲中我提到,数据库的连接是比较重的一类资源,不仅连接过程比较耗时,而且
连接 MySQL 的客户端数量有限制,最多可以设置为 16384(在实际的项目中,可以依据
实际业务来调整)。
第二点,一体化架构增加了研发的成本,抑制了研发效率的提升。(模块之间互相依赖,一个小团队中的成员犯了一个错误,就可能会影响到,其它团队维护的服务,对于整体系统稳定性影响很大)
第三点,一体化架构对于系统的运维也会有很大的影响。
如何使用微服务化解决这些痛点
之前,我在做一个社区业务的时候,开始采用的架构也是一体化的架构,数据库已经做了垂
直分库,分出了用户库、内容库和互动库,并且已经将工程拆分了业务池,拆分成了用户
池、内容池和互动池。
当前端的请求量越来越大时,我们发现,无论哪个业务池子,用户模块都是请求量最大的模
块儿,用户库也是请求量最大的数据库。这很好理解,无论是内容还是互动,都会查询用户
库获取用户数据,所以,即使我们做了业务池的拆分,但实际上,每一个业务池子都需要连
接用户库,并且请求量都很大,这就造成了用户库的连接数比其它都要多一些,容易成为系
统的瓶颈。可以将内容和互动相关的逻辑,都独立出来,形成内容服务和互动服务,这样,我们就通过按照业务做横向拆分的方式,解决了数据库层面的扩展性问题
在架构演进的初期和中期,性能、可用性、可扩展性是我们追求的主
要目标,高性能和高可用给用户带来更好的使用体验,扩展性可以方便我们支撑更大量级的
并发。但是当系统做的越来越大,团队成员越来越多,我们就不得不考虑成本了
微服务架构篇
其实就是将整体工程,重构甚至重写的过程。你需要将代码,拆分到若干个子工程里面,再将这些子工程,通过一些通信方式组装起来,
在开始拆分之前,你需要明确几个拆分的原则,否则就会事倍功半,甚至对整体项目产
生不利的影响
原则一,做到单一服务内部功能的高内聚,和低耦合。也就是说,每个服务只完成自己职责
之内的任务,对于不是自己职责的功能,交给其它服务来完成。说起来你可能觉得理所当
然,对这一点不屑一顾,但很多人在实际开发中,经常会出现一些问题。
原则二,你需要关注服务拆分的粒度,先粗略拆分,再逐渐细化。
原则三,拆分的过程,要尽量避免影响产品的日常功能迭代,也就是说,要一边做产品功能
迭代,一边完成服务化拆分,我们的拆分只能在现有一体化系统的基础上,不断剥离业务独立部署,剥离的顺序,你可以参考以下几点:
- 优先剥离比较独立的边界服务(比如短信服务、地理位置服务),从非核心的服务出
发,减少拆分对现有业务的影响,也给团队一个练习、试错的机会; - 当两个服务存在依赖关系时,优先拆分被依赖的服务。比方说,内容服务依赖于用户服
务获取用户的基本信息,那么如果先把内容服务拆分出来,内容服务就会依赖于一体化架构
中的用户模块,这样还是无法保证内容服务的快速部署能力。所以正确的做法是,你要理清服务之间的调用关系,比如说,内容服务会依赖用户服务获取用户信息,互动服务会依赖内容服务,所以要按照先用户服务,再内容服务,最后互动服务的顺序来进行拆分。
原则四,服务接口的定义要具备可扩展性。服务拆分之后,由于服务是以独立进程的方式部
署,所以服务之间通信,就不再是进程内部的方法调用,而是跨进程的网络通信了。在这种
通信模型下需要注意,服务接口的定义要具备可扩展性,否则在服务变更时,会造成意想不
到的错误。
核心组件:RPC 框架。
服务拆分单独部署后,引入的服务跨网络通信的问题;
在拆分成多个小服务之后,服务如何治理的问题。
RPC(Remote Procedure Call,远程过程调用),你不会陌生,它指的是通过网
络,调用另一台计算机上部署服务的技术。
选择合适的序列化方式
如何选择这几种序列化协议呢?这里我给你几点建议:
如果对于性能要求不高,在传输数据占用带宽不大的场景下,可以使用 JSON 作为序列
化协议;
如果对于性能要求比较高,那么使用 Thrift 或者 Protobuf 都可以。而 Thrift 提供了配
套的 RPC 框架,所以想要一体化的解决方案,你可以优先考虑 Thrift;
在一些存储的场景下,比如说你的缓存中存储的数据占用空间较大,那么你可以考虑使
用 Protobuf 替换 JSON,作为存储数据的序列化方式。
成熟的 RPC 框架的源代码。比如,阿里开源
的 Dubbo,微博的 Motan 等等,理解它们的实现原理和细节,这样你会更有信心维护好
你的微服务系统;同时,你也可以从优秀的代码中,学习到代码设计的技巧,比如说
Dubbo 对于 RPC 的抽象,SPI 扩展点的设计,这样可以有助你提升代码能力。
常见的负载均衡策略有哪些
一类是静态策略,也就是说负载均衡服务器在选择服务节点时,不会参考后端服务的实
际运行的状态。
一类是动态策略,也就是说负载均衡服务器会依据后端服务的一些负载特性,来决定要
选择哪一个服务节点
常见的静态策略有几种,其中使用最广泛的是轮询的策略(RoundRobin,RR),这种策
略会记录上次请求后端服务的地址或者序号,然后在请求时,按照服务列表的顺序,请求下
一个后端服务节点。
轮询的策略可以做到
将请求尽量平均地分配到所有服务节点上,但是,它没有考虑服务节点的具体配置情况。
所以,我们考虑给节点加上权重值,比如给 8 核 8G 的机器配置权重为 2,那么就会给它分
配双倍的流量,这种策略就是带有权重的轮询策略。
注册中心:分布式系统如何寻址?
API 网关也有很多开源的实现
1、Kong是在 Nginx 中运行的 Lua 程序。
用 RPC 框架解决服务通信的问题;
用注册中心解决服务注册,和发现的问题;
使用分布式 Trace 中间件,排查跨服务调用慢请求;
使用负载均衡服务器,解决服务扩展性的问题;
全链路的压力测试系统
对于我们来说有三方面的价值:其一,它可以帮助我们发现系统
中可能出现的性能瓶颈,方便我们提前准备预案来应对;其次,它也可以为我们做容量评
估,提供数据上的支撑;最后,我们也可以在压测的时候做预案演练,因为压测一般会安排
在流量的低峰期进行,这样我们可以降级一些服务来验证预案效果,并且可以尽量减少对线
上用户的影响。所以,随着你的系统流量的快速增长,你也需要及时考虑搭建这么一套全链
路压测平台,来保证你的系统的稳定性。
- 压力测试是一种发现系统性能隐患的重要手段,所以应该尽量使用正式的环境和数据;
- 对压测的流量需要增加标记,这样就可以通过 Mock 第三方依赖服务和影子库的方式来
实现压测数据和正式数据的隔离; - 压测时,应该实时地对系统性能指标做监控和告警,及时地对出现瓶颈的资源或者服务
扩容,避免对正式环境产生影响。
局部故障最终导致全局故障,这种情况有一个专业的名词儿,叫做“雪崩”。
解决的思路就是在检测到某一个
服务的响应时间出现异常时,切断调用它的服务与它之间的联系,让服务的调用快速返回失
败,从而释放这次请求持有的资源。这个思路也就是我们经常提到的降级和熔断机制。
- 限流是一种常见的服务保护策略,你可以在整体服务、单个服务、单个接口、单个 IP 或
者单个用户等多个维度进行流量的控制; - 基于时间窗口维度的算法有固定窗口算法和滑动窗口算法,两者虽然能一定程度上实现
限流的目的,但是都无法让流量变得更平滑; - 令牌桶算法和漏桶算法则能够塑形流量,让流量更加平滑,但是令牌桶算法能够应对一
定的突发流量,所以在实际项目中应用更多。