风行的博客

了解一点互联网架构

早期互联网应用通常用户少、并发低、数据量也比较小,所以将应用程序、文件服务、数据库服务都集中部署在一台服务器上就能够满足需求,这种方式易于开发和部署,但它做不到高可用和高并发,当服务器出现故障或项目升级时需要停止服务。

随着时代发展,用户量及数据量快速膨胀,访问量也越来越多,于是对系统的高可用和高并发等方面都有了更高要求,互联网应用架构逐渐发展成需要集群部署以及分布式部署来满足需求。

  • 集群部署可以简单理解成多台机器部署相同服务,它是用来解决高可用、高并发以及海量数据问题的常用手段,在使用时需要考虑负载均衡、异地多活、容灾备份、以及熔断隔离等问题。

  • 分布式部署可以简单理解成多台机器部署不同服务,使用时需要考虑各服务间的通信等问题,如 RPC,消息队列以及分布式事务和分布式锁。

下图为58架构师沈剑画的互联网应用分层架构图(侵删),将互联网架构分为以下几层:客户端层、反向代理层、Web应用层、服务层和数据层

客户端层

客户端层的主体通常是浏览器、app 和小程序,它们的主要任务就是向用户展示数据并提供所需的数据交互功能,它会频繁的与Web应用层交换数据,在这过程中客户端会向 DNS 服务器发起域名解析请求,如果 DNS 服务器指向了某个 CDN 服务,则会将域名解析操作交给 CDN 中的 DNS 负载均衡系统处理,在识别出访问者是北京某联通用户后,会根据用户 IP 将离用户最近的联通机房中部署的反向代理服务集群中的某个服务器外网地址返回,该地址在被访问时会实施负载均衡策略并将请求转发给内网中Web应用集群中的某一台服务器。

当系统遇到性能问题时,通常会对Web服务器做集群,然后通过反向代理去做负载均衡,当系统吞吐超过反向代理的性能极限并且难以扩容时,可以在 DNS 服务器上对同一个域名配置多个反向代理服务器 IP,然后每次 DNS 解析请求时通过轮询或其他方式返回不同 IP,从而达到负载均衡目的,大型网站通常会使用 DNS 来做一级负载均衡,然后再在内部做第二级负载均衡。

一些访问量较高的网站、直播、视频平台都会利用 CDN 来解决不同地域不同网络访问静态资源的速度问题,当用户就近的缓存服务器没有相关资源时才会访问源服务器获取资源,并且根据策略决定是否更新缓存数据。

反向代理层

通常代理服务器是相对于用户来说的,它可以代替用户去获取网络信息,从而隐藏真实的客户端信息(VPN),而反向代理的目的是为了隐藏Web服务器,它以代理服务器的身份来接受网络连接请求,然后根据负载均衡策略将请求转发给内网上的某台服务器上,以解决系统面临大量用户访问时负载过高的问题,在转发过程中负载均衡技术能够提供服务器的失效检测功能,然后将用户请求转发到可用节点上,从而保证所有服务持续可用

负载均衡技术包括 DNS 负载均衡、硬件负载均衡和软件负载均衡,其中硬件负载均衡是通过网络设备硬件来抗压力,虽然性能好但是太贵,很多公司负担不起,基于软件做的负载均衡只需要在正常服务器上部署软件即可,它还分为四层协议和七层协议两种:

  • 基于第四层传输层来做流量分发的称为四层负载均衡,例如 LVS(Linux Virtual Server),四层负载均衡服务器在接受到客户端请求后通过修改数据包的地址信息(IP + 端口号)将流量转发出去
  • 基于第七层应用层来做流量分发的称为七层负载均衡,例如 Nginx 反向代理软件,它通过代理方式实现负载均衡

第四层的负载均衡性能相对来说要高一些,但对于一般应用来说七层就够用了,一些大型网站通常会采用 DNS + 四层 + 七层负载的方式进行多层次负载均衡

常用负载均衡策略包括:

  • 轮询:将请求轮流发送到每个服务器上,这适合每台服务器性能差不多的场景,如果有性能差异,那么性能较差的服务器可能无法承担所分配的负载
  • 加权轮询:在轮询的基础上,根据服务器的性能差异,为服务器赋予一定的权重,性能高的服务器分配更高的权重
  • 最少连接:由于每个请求的连接时间不一样,使用轮询或者加权轮询方式可能会使某个服务器当前连接数过大,而有的服务器连接过小造成负载不均衡,最少连接策略就是将请求发送给当前最少连接数的服务器上
  • 加权最少连接:在最少连接的基础上,根据服务器的性能为每台服务器分配权重,再根据权重计算出每台服务器能处理的连接数
  • 源地址哈希:通过对客户端 IP 计算哈希值后,再对服务器数量取模得到目标服务器的序号,这样可以保证同一 IP 的客户端的请求会转发到同一台服务器上

Web应用层

Web应用层通常由多个服务器形成一个或多个集群来向客户端提供服务,当请求量非常大时便要求系统具有处理高并发的能力,并且能够保障一定的高可用性,在高可用上可通过异地多活、容灾备份、以及熔断隔离等方式来保障,高并发上可通过服务降级、限流降级等方式处理。

  • 服务降级:系统为了应对大量请求会主动关闭部分功能或者降低功能,从而保证核心功能可用,例如电商网站在流量高峰时可以将修改收货地址、退单等非核心功能禁用,还可以限制爬虫流量,对其返回空数据或跳转到某静态页面

  • 限流降级:当瞬间并发非常高时可以进行限流处理,例如秒杀服务开始时会根据令牌桶算法来控制流量,当数量超过商品数量时便可以向后续访问用户提示需要排队等待或者拒绝服务

  • 异地多活:大公司通常会把服务部署在不同地区的多个机房中,当某个机房发生例如停电等故障导致服务不能正常使用时,DNS 负载均衡服务会将请求全部切到另一个机房

  • 熔断:在家里使用电时当线路电压过高,保险丝会自己熔断以防止火灾,映射到软件系统中指的是当某个服务调用慢或发生大量请求超时后对于新的调用请求不在继续调用该服务,当情况好转后并且过了静默期后再恢复调用

  • 隔离:造船行业有一个专业术语叫做舱壁隔离,是指利用舱壁将不同的船舱隔离起来,如果某一个船舱进水,这时可以立即封闭舱门,可以做到只损失那一个船舱,保障其他船舱不受影响,所以整个船只还是可以正常航行。映射到软件系统中指的是当服务熔断或者发生其它故障时,能够限制问题的影响范围,从而保证只有出问题的业务不可用,其他服务可以正常使用,例如当网站的秒杀系统因抗不住挂了后,其他服务仍然可以继续使用

熔断和隔离都属于问题后的容错处理机制,而服务降级和限流降级则是一种预防模式。

服务层

RPC 与消息队列都是分布式系统中服务之间的通信方式,RPC 可以像调用本地函数一样调用网络中另一台服务器提供的服务,消息队列是基于发布订阅模式,消息队列服务器将接收到的消息写入本地内存队列后立即返回成功给消息生产者,然后再根据消息订阅列表查找订阅该消息的订阅者后按照先进先出的原则将队列中的消息发送出去,消息队列具有以下优点

  • 应用解耦:发送者将消息发送至消息队列即结束对消息的处理,订阅者从消息队列获取该消息后进行后续处理,并不需要知道该消息的来源

  • 异步处理:发送者将消息发送给消息队列后不需要同步等待订阅者处理完毕,而是立即返回并进行其它操作

  • 流量削锋:在访问高峰时,可以将消息暂存于消息队列中,从而减轻了数据库的存储负载压力,服务器按照其处理能力从消息队列中订阅消息并进行处理

当调用方需要关心执行结果时通常使用 RPC,不关心执行结果仍使用 RPC 调用会造成服务之间耦合以及响应速度慢等问题,这时比较适合使用消息队列,例如用户注册账户后的发送验证邮件问题,如果必须完成验证邮件中的提示操作才算完成注册则需要在注册操作后通过 RPC 来调用发邮件服务,否则应该使用消息队列。

数据层

数据层作为系统的核心,在设计时需要考虑高并发和大数据的查询效率问题,在做优化时可先考虑加缓存,然后才是数据库的读写分离和分库分表。

缓存

大部分互联网业务都是读多写少,数据库的读操作往往会成为性能瓶颈,引入缓存层后如果用户在访问时能够命中缓存,便能够减小数据库的访问压力又可以提高访问速度,所以优化缓存命中率以及缓存与数据的一致性就变的很重要,一般可通过淘汰过期缓存以及命中率低的缓存数据来提高命中率。

使用缓存还需要考虑当发生缓存穿透以及缓存雪崩时的处理办法,缓存穿透指的是对缓存中不存在的数据进行请求,该请求将会穿透缓存到达数据库,当相关流量非常大或有人故意攻击时会造成缓存雪崩,从而导致数据库无法处理这么大的请求而崩溃。

读写分离

系统在使用缓存后仍然会有一部分请求因缓存没命中而将读操作直接操作在数据库上,在数据库读写并发量高时,数据库便会因负载压力过大而影响性能,所以一般会将业务数据库做读写分离,即分为一个主库以及一个或多个从库,写请求访问主数据库,读请求访问从数据库,从而改善数据库负载压力,主数据库会通过主从复制机制将数据更新同步到从数据库。

分库分表

分库分表即是把原本存储于一个库的数据按业务分库后存储到多个服务器上,把原本存储于一个表的数据分块存储到多个表上,常见的分表方式:

  • 垂直切分:当单个表中的字段非常多时可以按表中字段的访问频率高低来拆表,此方式通常与业务结合比较紧密,并不是所有业务都适合垂直切分

  • 水平切分:当单个表中的数据量非常大时可以在数据库中创建多个相同结构的表,然后按范围法哈希法将原表中的数据拆分后填充进去

    • 范围法:order_0 表存储 id 范围 1-1kw,order_1 表存储 id 范围 1kw-2kw,以此类推
    • 哈希法:存储某个 key 值 hash 后的部分数据, order_0 表存储 id % 4 = 0 的数据,order_1 表存储 id % 4 = 1 的数据,以此类推

范围法对于数据库扩展性上较好,可以随时加一个 order_3 用来存储 2kw-3kw 的数据,缺点是负载不一定均衡,新订单所在的表被访问的频率会非常高,哈希法相对来说负载较均衡,使用较广泛