天天看点

高并发架构实战课

01 软件建模

一个是我们要解决的领域问题。比如我们要开发一个电子商务网站,那么客观的领域问题就是如何做生意,卖家如何管理商品、管理订单、服务用户,买家如何挑选商品,如何下订单,如何支付等等。对这些客观领域问题的抽象就是各种功能及其关系、各种模型对象及其关系、各种业务处理流程。

另一个客观存在就是最终开发出来的软件系统。软件系统要解决的问题包括软件由哪些主要类组成,这些类如何组织构成一个个的组件,这些类和组件之间的依赖关系如何,运行期如何调用,需要部署多少台服务器,服务器之间如何通信等。

高并发架构实战课
高并发架构实战课
高并发架构实战课
高并发架构实战课
高并发架构实战课
高并发架构实战课
高并发架构实战课
高并发架构实战课

+++++++++++++++++++++++++++++++++++++++++++

高性能架构

高并发架构实战课
高并发架构实战课
高并发架构实战课
高并发架构实战课
高并发架构实战课
高并发架构实战课

Proactor 模型示意图:

高并发架构实战课

理论上 Proactor 比 Reactor 效率要高一些,异步 I/O 能够充分利用 DMA 特性,让 I/O 操作与计算重叠,但要实现真正的异步 I/O,操作系统需要做大量的工作。目前 Windows 下通过 IOCP 实现了真正的异步 I/O,而在 Linux 系统下的 AIO 并不完善,因此在 Linux 下实现高并发网络编程时都是以 Reactor 模式为主。所以即使 Boost.Asio 号称实现了 Proactor 模型,其实它在 Windows 下采用 IOCP,而在 Linux 下是用 Reactor 模式(采用 epoll)模拟出来的异步模型

++++++++++++++++++++++++++++++++++++

高并发架构实战课
高并发架构实战课
高并发架构实战课
高并发架构实战课

++++++++++++++++++++++++++++++++++++++++

我们考虑在业务服务器的上层增加一层特殊的缓存,用来承担绝大部分对于静态资源的访问,这一层特殊缓存的节点需要遍布在全国各地,这样可以让用户选择最近的节点访问。缓存的命中率也需要一定的保证,尽量减少访问资源存储源站的请求数量(回源请求)。这一层缓存就是我们这节课的重点:CDN

CDN 的关键技术

CDN(Content Delivery Network/Content Distribution Network,内容分发网络)。简单来说,CDN 就是将静态的资源分发到位于多个地理位置机房中的服务器上,因此它能很好地解决数据就近访问的问题,也就加快了静态资源的访问速度

高并发架构实战课
高并发架构实战课
高并发架构实战课
高并发架构实战课
高并发架构实战课
高并发架构实战课
高并发架构实战课

当然,是否能够从 CDN 节点上获取到资源还取决于 CDN 的同步延时。一般我们会通过 CDN 厂商的接口将静态的资源写入到某一个 CDN 节点上,再由 CDN 内部的同步机制将资源分散同步到每个 CDN 节点,即使 CDN 内部网络经过了优化,这个同步的过程是有延时的,一旦我们无法从选定的 CDN 节点上获取到数据,我们就不得不从源站获取数据,而用户网络到源站的网络可能会跨越多个主干网,这样不仅性能上有损耗也会消耗源站的带宽,带来更高的研发成本。所以我们在使用 CDN 的时候需要关注 CDN 的命中率和源站的带宽情况。

+++++++++++++++++++++++++++++++++++++++++++++

高并发系统实战课

01 数据表优化

高并发架构实战课
高并发架构实战课
高并发架构实战课
高并发架构实战课
高并发架构实战课
高并发架构实战课
高并发架构实战课
高并发架构实战课

02 缓存一致性

我们做缓存是要考虑性价比的。如果我们费时费力地把一些数据放到缓存当中,但并不能提高系统的性能,反倒让我们浪费了大量的时间和金钱,那就是不合适的。我们需要评估缓存是否有效,一般来说,只有热点数据放到缓存才更有价值。

高并发架构实战课
高并发架构实战课
高并发架构实战课

03 Token:降低用户身份鉴权的流量压力

网站初期通常会用 Session 方式实现登录用户的用户鉴权,也就是在用户登录成功后,将这个用户的具体信息写在服务端的 Session 缓存中,并分配一个 session_id 保存在用户的 Cookie 中。该用户的每次请求时候都会带上这个 ID,通过 ID 可以获取到登录时写入服务端 Session 缓存中的记录

高并发架构实战课

全站的请求都会对这个缓存至少访问一次,这就导致缓存的内容长度和响应速度,直接决定了全站的 QPS 上限,让整个系统的隔离性很差,各子系统间极易相互影响。

JWT 登陆和 token 校验

常见方式是采用签名加密的 token,这是登录的一个行业标准,即 JWT(JSON Web Token):

高并发架构实战课

上图就是 JWT 的登陆流程,用户登录后会将用户信息放到一个加密签名的 token 中,每次请求都把这个串放到 header 或 cookie 内带到服务端,服务端直接将这个 token 解开即可直接获取到用户的信息,无需和用户中心做任何交互请求。

这个 token 内部包含过期时间,快过期的 token 会在客户端自动和服务端通讯更换,这种方式可以大幅提高截取客户端 token 并伪造用户身份的难度

同时,服务端也可以和用户中心解耦,业务服务端直接解析请求带来的 token 即可获取用户信息,无需每次请求都去用户中心获取。而 token 的刷新可以完全由 App 客户端主动请求用户中心来完成,而不再需要业务服务端业务请求用户中心去更换。

高并发架构实战课

JWT 的 token 解密很简单,第一段和第二段都是通过 base64 编码的。直接解开这两段数据就可以拿到 payload 中所有的数据,其中包括用户昵称、uid、用户权限和 token 过期时间。要验证 token 是否过期,只需将其中的过期时间和本地时间对比一下,就能确认当前 token 是不是有效。

而验证 token 是否合法则是通过签名验证完成的,任何信息修改都会无法通过签名验证。要是通过了签名验证,就表明 token 没有被篡改过,是一个合法的 token,可以直接使用。

高并发架构实战课

我们可以看到,通过 token 方式,用户中心压力最大的接口可以下线了,每个业务的服务端只要解开 token 验证其合法性,就可以拿到用户信息。不过这种方式也有缺点,就是用户如果被拉黑,客户端最快也要在 token 过期后才能退出登陆,这让我们的管理存在一定的延迟。

如果我们希望对用户进行实时管理,可以把新生成的 token 在服务端暂存一份,每次用户请求就和缓存中的 token 对比一下,但这样很影响性能,极少数公司会这么做。同时,为了提高 JWT 系统的安全性,token 一般会设置较短的过期时间,通常是十五分钟左右,过期后客户端会自动更换 token。

高并发架构实战课
高并发架构实战课

可以看到,这个方案里有两种 token:一种是 refresh_token,用于更换 access_token,有效期是 30 天;另一种是 access_token,用于保存当前用户信息和权限信息,每隔 15 分钟更换一次。如果请求用户中心失败,并且 App 处于离线状态,只要检测到本地 refresh_token 没有过期,系统仍可以继续工作,直到 refresh_token 过期为止,然后提示用户重新登陆。这样即使用户中心坏掉了,业务也能正常运转一段时间。

高并发架构实战课

04同城双活:实现机房之间的数据同步

高并发架构实战课

在这个方案中,数据库主库集中在一个机房,其他机房的数据库都是从库。当有数据修改请求时,核心机房的主库先完成修改,然后通过数据库主从同步把修改后的数据传给备份机房的从库。由于用户平时访问的信息都是从缓存中获取的,为了降低主从延迟,备份机房会把修改后的数据先更新到本地缓存。

高并发架构实战课

这是一个相对简单的设计,但缺点也很多。比如如果核心机房离线,其他机房就无法更新,故障期间需要人工切换各个 proxy 内的主从库配置才能恢复服务,并且在故障过后还需要人工介入恢复主从同步。

此外,因为主从同步延迟较大,业务中刚更新的数据要延迟一段时间,才能在备用机房查到,这会导致我们业务需要人工兼顾这种情况,整体实现十分不便。

跨机房同步神器:OtterOtter

是阿里开发的数据库同步工具,它可以快速实现跨机房、跨城市、跨国家的数据同步。如下图所示,其核心实现是通过 Canal 监控主库 MySQL 的 Row binlog,将数据更新并行同步给其他机房的 MySQL。

高并发架构实战课

因为我们要实现同城双机房双活,所以这里我们用 Otter 来实现同城双主(注意:双主不通用,不推荐一致要求高的业务使用),这样双活机房可以双向同步:

高并发架构实战课
高并发架构实战课
高并发架构实战课

+++++++++++++++++++++++++++++++++++++++++++

写多读少的存储选型:

高并发架构实战课
高并发架构实战课
高并发架构实战课
高并发架构实战课
高并发架构实战课
高并发架构实战课

每一层的数据块和数据量超过一定程度时,RocksDB 合并不同 Level 的数据,将多个数据块内的数据和索引合并在一起,并推送到 Level 的下一层。通过这个方式,每一层的数据块个数和数据量就能保持一定的数量,合并后的数据会更紧密、更容易被找到

高并发架构实战课

可以看到,这个方式虽然放弃了整体索引的一致性,却换来了更高效的写性能。在读取时通过遍历所有子树来查找,减少了写入时对树的合并代价。

LSM 这种方式的数据存储在 OLAP 数据库中很常用,因为 OLAP 多数属于写多读少,而当我们使用 OLAP 对外提供数据服务的时候,多数会通过缓存来帮助数据库承受更大的读取压力。

高并发架构实战课

而目前常见的 HTAP 实现方式,普遍采用一个服务集群内同一套数据支持多种数据存储方式(行存储、列存储),通过对数据提供不同的索引来实现 OLAP 及 OLTP 需求,而用户在查询时,可以指定或由数据库查询引擎根据 SQL 和数据情况,自动选择使用哪个引擎来优化查询。

++++++++++++++++++++++++++++++++++++++++++++

ElasticSearch架构

高并发架构实战课

我们对照架构图,梳理一下整体的数据流向,可以看到,我们项目产生的日志,会通过 Filebeat 或 Rsyslog 收集将日志推送到 Kafka 内。然后由 LogStash 消费 Kafka 内的日志、对日志进行整理,并推送到 ElasticSearch 集群内。

高并发架构实战课

Elasticsearch 的写存储机制

高并发架构实战课
高并发架构实战课
高并发架构实战课

Elasticsearch 服务为了保证读写性能可扩容,Elasticsearch 对数据做了分片,分片的路由规则默认是通过日志 DocId 做 hash 来保证数据分布均衡,常见分布式系统都是通过分片来实现读写性能的线性提升。

高并发架构实战课
高并发架构实战课

Lucene的实现

高并发架构实战课
高并发架构实战课

++++++++++++++++++++++++++++++++++++++++++

16 本地缓存实现

   集中缓存在读多写多的场景中有上限,当流量达到一定程度,集中式缓存和无状态服务的大量网络损耗会越来越严重,这导致高并发读写场景下,缓存成本高昂且不稳定

为了降低成本、节省资源,我们会在业务服务层再增加一层缓存,放弃强一致性,保持最终一致性,以此来降低核心缓存层的读写压力。

高并发架构实战课
高并发架构实战课
高并发架构实战课
高并发架构实战课

当然行业也存在一些无锁的黑科技,这些方法都可以减少我们的锁争抢,比如 atomic、Go 的 sync.Map、sync.Pool、Java 的 volidate。感兴趣的话,你可以找自己在用的语言查一下相关知识。除此之外,无锁实现可以看看 MySQL InnoDB 的 MVCC。

高并发架构实战课
高并发架构实战课

+++++++++++++++++++++++++++++++++++++++++++++

17.可编程订阅式缓存

高并发架构实战课

传统模式下,缓存服务不会对数据做任何加工,保存的是系列化的字符串,大部分的数据无法直接修改。当我们使用这种缓存对外进行服务时,业务服务需要将所有数据取出到本地内存,然后进行遍历加工方可使用。

而可编程缓存可以把数据结构化地存在 map 中,相比传统模式序列化的字符串,更节省内存。

Lua 脚本引擎

高并发架构实战课
高并发架构实战课
高并发架构实战课

18流量拆分:如何通过架构设计缓解流量压力

如何评估一个服务器支持多少人同时在线呢?

我们可以通过压测测出单台服务器的服务在线人数,以此精确地预估带宽和服务器资源,算出一个集群(集群里包括若干服务器)需要多少资源、可以承担多少人在线进行互动,再通过调度服务分配资源,将新来的房主分配到空闲的服务集群

高并发架构实战课

在创建房间阶段,我们的客户端在进入区域服务器集群之前,都是通过请求调度服务来进行调度的。调度服务器会定期接收各组服务器的服务用户在线情况,以此来评估需要调配多少用户进入到不同区域集群;同时客户端收到调度后,会拿着调度服务给的 token 去不同区域申请创建房间。

房间创建后,调度服务会在本地集群内维护这个房间的列表和信息,提供给其他要加入游戏的玩家展示。而加入的玩家同样会接入对应房间的区域服务器,与房主及同房间玩家进行实时互动

高并发架构实战课
高并发架构实战课
高并发架构实战课
高并发架构实战课

+++++++++++++++++++++++++++++++++++++++

19 流量调度:DNS、全站加速及机房负载均衡

DNS 域名解析及缓存

DNS 是我们发起请求的第一步,如果 DNS 缓慢或错误解析的话,会严重影响读多写多系统的交互效果。

高并发架构实战课
高并发架构实战课
高并发架构实战课
高并发架构实战课
高并发架构实战课
高并发架构实战课
高并发架构实战课

而网站做了全站加速后,所有的用户请求都会由 CDN 转发,而客户端请求的所有域名也都会指向 CDN,再由 CDN 把请求转到我们的服务端

在此期间,如果机房变更了 CDN 提供服务的 IP,为了加快 DNS 缓存刷新,可以使用 CDN 内网 DNS 的服务(该服务由 CDN 供应商提供)去刷新 CDN 中的 DNS 缓存。这样做客户端的 DNS 解析是不变的,不用等待 48 小时,域名刷新会更加方便

国外为了减少入口故障问题,配合使用了 anycast 技术。通过 anycast 技术,就能让多个机房服务入口拥有同样的 IP,如果一个入口发生故障,运营商就会将流量转发到另外的机房。但是,国内因为安全原因,并不支持 anycast 技术

除了 CDN 入口出现故障的风险外,请求流量进入 CDN 后,CDN 本地没有缓存回源而且本地网站服务也发生故障时,也会出现不能自动切换源到多个机房的问题。所以,为了加强可用性,我们可以考虑在 CDN 后面增加 GTM。

GTM 全局流量管理

高并发架构实战课
高并发架构实战课

GTM 和 CDN 网站加速结合后会有更好的效果

高并发架构实战课

由于 GTM 和 CDN 加速都是用了 CNAME 做转发,我们可以先将域名指向 CDN,通过 CDN 的 GSLB 和内网为客户端提供网络加速服务。而在 CDN回源时请求会转发到 GTM 解析,经过 GTM 解析 DNS 后,将 CDN 的流量转发到各个机房做负载均衡。

当我们机房故障时,GTM 会从负载均衡列表快速摘除故障机房,这样既满足了我们的网络加速,又实现了多机房负载均衡及更快的故障转移

HttpDNS 服务

HttpDNS 服务能够帮助我们绕过本地 ISP 提供的 DNS 服务,防止 DNS 劫持,并且没有 DNS 域名解析刷新的问题。同样地,HttpDNS 也提供了 GSLB 功能。HttpDNS 还能够自定义解析服务,从而实现灰度或 A/B 测试。

不过 HttpDNS 这个服务不是免费的,尤其对大企业来说成本更高,因为很多 HttpDNS 服务商提供的查询服务会按请求次数计费。

所以,为了节约成本我们会设法减少请求量,建议在使用 App 时,根据客户端链接网络的 IP 以及热点名称(Wifi、5G、4G)作为标识,做一些 DNS 缓存。

业务自实现流量调度

为了让用户体验更好,互联网公司结合 HttpDNS 的原理实现了流量调度,比如很多无法控制用户流量的直播服务,就实现了类似 HttpDNS 的流量调度服务。调度服务常见的实现方式是通过客户端请求调度服务,调度服务调配客户端到附近的机房。

为了提高客户端的用户体验,我们需要给客户端调配到就近的、响应性能最好的机房,为此我们需要一些辅助数据来支撑调度服务分配客户端,这些辅助数据包括 IP、GPS 定位、网络服务商、ping 网速、实际播放效果。

客户端会定期收集这些数据,反馈给大数据中心做分析计算,提供参考建议,帮助调度服务更好地决策当前应该链接哪个机房和对应的线路。

为了验证调度是否稳定,我们可以在客户端暂存调度结果,每次客户端请求时在 header 中带上当前调度的结果,通过这个方式就能在服务端监控有没有客户端错误请求到其他机房的情况。

高并发架构实战课

++++++++++++++++++++++++++++++++++++++++++

继续阅读