系统设计

系统设计入门概念普及。

高质量设计目标

性能

性能指标

  • 响应时间:指某个请求从发出到接收到响应消耗的时间。在对响应时间进行测试时,通常采用重复请求的方式,然后计算平均响应时间
  • 吞吐量(TPS):指系统在单位时间内可以处理的请求数量,通常使用每秒的请求数来衡量
  • 并发用户数:指系统能同时处理的并发用户请求数量
    • 在没有并发存在的系统中,请求被顺序执行
    • 目前的大型系统都支持多线程来处理并发请求,多线程能够提高吞吐量以及缩短响应时间,主要是由于多CPU的引入以及IO多路复用提高效率

性能优化

  • 集群:将多台服务器组成集群,使用负载均衡将请求转发到集群中,避免单一服务器的负载压力过大导致性能降低。
  • 缓存:提高性能
    • 数据通常位于内存等介质中,这种介质对于读操作特别快
    • 数据可以位于靠近用户的地理位置上
    • 可以将计算结果进行缓存,从而避免重复计算
  • 异步:某些流程可以将操作转换为消息,将消息发送到消息队列之后立即返回,之后这个操作会被异步处理

服务化

按照“自包含、可重用、独立管理”三原则,将网络功能定义为若干个可被灵活调用的“服务”模块。

优点:

  • 敏捷:服务松耦合,网络部署、维护、升级更快速、便利
  • 易扩展:轻量级的接口使得新功能的引入不需要引入新的接口设计
  • 灵活:通过模块化、可重用方式实现网络功能的组合,满足网络切片等灵活组网需求
  • 开放:新型REST API接口极大的便于运营商或第三方调用服务

异步

异步架构通过消息队列构建。消息的生产者将消息发送到消息队列以后,由消息的消费者从消息队列中获取消息,然后进行业务逻辑的处理,消息的生产者和消费者是异步处理的,彼此不会等待阻塞,所以叫做异步架构。

两种模型:

  1. 点对点模型 消费者和生产者只需要知道消息队列的名字,生产者发送消息到消息队列中,而消息队列的另一端是多个消费者竞争消费消息,每个到达消息队列的消息只会被路由到一个消费者中去。

  2. 发布订阅模型 生产者发送消息到一个主题,消息被发布到主题后,就会被克隆给每一个订阅它的消费者,每个消费者接收一份消息复制到自己的私有队列。消费者可以独立于其他消费者使用自己订阅的消息,消费者之间不会竞争消息。

优点:

  • 实现异步处理,提升处理性能
  • 让系统获得更好的伸缩性
  • 平衡流量峰值
  • 使生产者和消费者的代码实现解耦合

优秀产品:

RabbitMQ,开源的消息代理和队列服务器,用来通过普通协议在完全不同的应用之间共享数据,或者简单的将作业排队以便让分布式服务器进行处理。

高并发

常用的手段有:

  • 消息队列:解耦和削峰,应用之间通过消息传递进行通信
  • 读写分离:将数据库的读和写操作分不到不同的数据库节点上。主服务器负责写,从服务器负责读。另外,一主一从或者一主多从都可以。读写分离可以大幅提高读性能,小幅提高写的性能。因此,读写分离更适合单机并发读请求比较多的场景
  • 分库分表:解决由于库、表数据量过大,而导致数据库性能持续下降的问题
  • 负载均衡: 将任务比如用户请求处理分配到多个服务器处理以提高网站、应用或者数据库的性能和可靠性。常见的负载均衡系统包括3种:
    • DNS负载均衡:一般用来实现地理级别的均衡
    • 硬件负载均衡:通过单独的硬件设备比如 F5 来实现负载均衡功能(硬件的价格一般很贵)
    • 软件负载均衡:通过负载均衡软件比如 Nginx 来实现负载均衡功能

高可用

“高可用性”(High Availability)通常来描述一个系统经过专门的设计,从而减少停工时间,而保持其服务的高度可用性。简而言之,就是不间断对外提供服务。

为了保证高可用性,常用的手段有:

  • 限流:限流是从用户访问压力的角度来考虑如何应对系统故障。限流为了对服务端的接口接受请求的频率进行限制,防止服务挂掉。比如某一接口的请求限制为 100 个每秒, 对超过限制的请求放弃处理或者放到队列中等待处理
  • 降级:从系统功能优先级的角度考虑如何应对系统故障。服务降级指的是当服务器压力剧增的情况下,根据当前业务情况及流量对一些服务和页面有策略的降级,以此释放服务器资源以保证核心任务的正常运行
  • 熔断:熔断和降级是两个比较容易混淆的概念。熔断的目的在于应对当前系统依赖的外部系统或者第三方系统的故障
  • 排队:可以看作是另类的限流方式,控制单位时间提供服务的数量
  • 集群:相同的服务部署多份,避免单点故障
  • 超时和重试机制:一旦用户的请求超过某个时间得不到响应就结束此次请求并抛出异常

具体到核心技术:

  • CDN加速,服务器上存储的静态内容缓存在云CDN节点上,用户无需访问服务器源站,就近访问云CDN节点即可获取相同内容,既能达到加速的效果,又能减轻服务器源站的压力。
  • 缓存,比如使用redis作为缓存服务器,用户的访问流量在到达数据库之前直接过滤掉一大部分
  • 负载均衡,将流量分发到多个服务器
  • 拆分服务,降低服务之间的耦合程度

伸缩性

指不断向集群中添加服务器来缓解不断上升的用户并发访问压力和不断增长的数据存储需求。

应用服务器只要不具有状态,那么就可以很容易地通过负载均衡器向集群中添加新的服务器。

关系型数据库的伸缩性通过 Sharding 来实现,将数据按一定的规则分布到不同的节点上,从而解决单台存储服务器的存储空间限制。

可扩展

核心:把大的系统拆分为小的系统

  • 面向流程拆分
    • 把不同模块按照职责分开,每层中的组件只处理本层的逻辑。这样就可以支持系统在某层上的快速扩展
  • 面向服务拆分
    • 微服务
  • 面向功能拆分
    • 微内核,区分出核心系统和插件模块

分布式系统

简介与优点

分布式系统是一个建立于网络上的系统,其组成部分位于不同的联网计算机上(表示为节点),它们通过相互传递信息来沟通和协调行动。

分布式系统应具有以下四个特征:

  • 分布性。分布式系统由多台计算机组成,它们在地域上是分散的,可以散布在一个单位、一个城市、一个国家,甚至全球范围内。整个系统的功能是分散在各个节点上实现的,因而分布式系统具有数据处理的分布性。
  • 自治性。分布式系统中的各个节点都包含自己的处理机和内存,各自具有独立的处理数据的功能。通常,彼此在地位上是平等的,无主次之分,既能自治地进行工作,又能利用共享的通信线路来传送信息,协调任务处理。
  • 并行性。一个大的任务可以划分为若干个子任务,分别在不同的主机上执行。
  • 全局性。分布式系统中必须存在一个单一的、全局的进程通信机制,使得任何一个进程都能与其他进程通信,并且不区分本地通信与远程通信。同时,还应当有全局的保护机制。系统中所有机器上有统一的系统调用集合,它们必须适应分布式的环境。在所有CPU上运行同样的内核,使协调工作更加容易。

分布式系统的优点:

  • 多主机并发执行任务,效率高
  • 可扩展性好,如果工作量增大,用户只需要向网络中添加主机即可,而不用重新更新或设计系统
  • 容错率高,局部节点出现故障不影响整个系统的运行

分布式系统单节点故障处理

  • 集群管理的服务器会将通向该节点上的连接重定向到其他节点上
  • 每一个节点的数据都会被事先复制到其他的所有节点上,因此可以从其他节点的备份数据里获取到需要的信息;也可以用这些备份数据完成之后的数据恢复

分布式系统下的Session问题

分布式状态下Session的问题:负载均衡多服务器的情况,不好确认当前用户是否登录,因为多服务器不共享Session。

解决方法:

  • 把用户访问页面产生的Session放到Cookie里面,以Cookie为中转站,如果服务器没有Session信息就检查客户端Cookie里有没有,如果有就用Cookie里的信息同步 (简单,服务器负担小;Cookie可能会被禁用,并且安全性不高)
  • memcached,把web服务器中的内存组合起来,成为一个”内存池”,不管是哪个服务器产生的 Session都可以放到这个”内存池”中,其他的都可以使用
    • 服务器负担小,安全,内存读写快
    • 把内存分成固定大小的内存块,无法完全利用,会产生碎片或者溢出问题
  • Token, 客户端登陆传递信息给服务端,服务端收到后把用户信息加密(Token)传给客户端,客户端将Token存放于localStroage等容器中。客户端每次访问都传递Token,服务端解密Token,就知道这个用户是谁了。通过cpu加解密,服务端就不需要存储session占用存储空间

负载均衡

负载均衡的核心是将流量分发到多个服务器,常见的方法有:

  • 轮转法:将请求按顺序轮流分配到后端服务器上,不关心服务器实际的连接数和系统负载。在此基础上,还可以给配置高、负载低的机器配置更高的权重,配置低、负载高的机器分配较低的权重,通过加权轮转保证正常处理用户请求
  • 随机法:从后端服务器列表中随机选取一台服务器进行访问。当用户访问次数不断增大的时候,效果会越来越接近轮转法
  • IP哈希法:获取客户端的IP地址,通过哈希函数计算得到的一个数值,用该数值对服务器列表的大小进行取模运算,得到的结果便是客服端要访问服务器的序号。
  • 最小连接数法:根据后端服务器当前的连接情况,动态选取当前连接数最少的一台服务器来处理当前的请求,尽可能提高后端服务的利用效率

一致性哈希算法

假如有N个缓存服务器,通用方法是计算访问对象的哈希值,再通过取模运算均匀的映射到N个缓存上。但如果某一天一个缓存服务器挂了,或者是访问加重,多加了一台服务器,取模映射公式中的N就变成了N-1或N+1,这导致突然之间几乎所有缓存都失效了,因为客户请求对象时的哈希值往往依赖于节点数量,不同的哈希值导致找不到原来保存这个对象的服务器节点。

一致性哈希将整个哈希值空间组织成一个虚拟的圆环,如假设某哈希函数值空间为0 ~ 2-1(即哈希值是一个32位无符号整数)。每台服务器可以选择服务器的IP或主机名作为关键字进行哈希,这样就能确定其在哈希环上的位置。而数据key使用相同的函数Hash计算出哈希值,并确定此数据在环上的位置,从此位置沿环顺时针“行走”,第一台遇到的服务器就是其应该访问定位到的服务器。一致性哈希算法对于节点的增减都只需重定位环空间中的一小部分数据,具有较好的容错性和可扩展性。

consistent-hash.png

如果服务器发生变化:

  • 如果一台服务器不可用,则受影响的数据仅仅是此服务器到其环空间中前一台服务器(即沿着逆时针方向行走遇到的第一台服务器)之间的数据,它们会被定位到顺时针行走到达的第一台服务器;其它数据不会受到影响。
  • 如果增加一台服务器,则受影响的数据仅仅是新服务器到其环空间中前一台服务器(即沿着逆时针方向行走遇到的第一台服务器)之间的数据,它们会被定位到新服务器上;其它数据也不会受到影响。

同时,一致性哈希算法引入了虚拟节点机制,即对每一个服务节点计算多个哈希,每个计算结果位置都放置一个此服务节点,称为虚拟节点。数据定位算法不变,只是多了一步虚拟节点到实际节点的映射,例如定位到“Node A#1”、“Node A#2”、“Node A#3”三个虚拟节点的数据均定位到Node A上。这样就解决了服务节点少时数据倾斜的问题。

架构演进

单一应用架构

当网站流量很小时,只需一个应用,将所有功能都部署在一起,以减少部署节点和成本。此时,用于简化增删改查工作量的数据访问框架(ORM)是关键。

垂直应用架构

当访问量逐渐增大,单一应用增加机器带来的加速度越来越小,提升效率的方法之一是将应用拆成互不相干的几个应用,以提升效率。此时,用于加速前端页面开发的Web框架是关键。

分布式服务架构

当垂直应用越来越多,应用之间交互不可避免,将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心,使前端应用能更快速的响应多变的市场需求。此时,用于提高业务复用及整合的分布式服务框架(RPC)是关键。

流动计算架构

当服务越来越多,容量的评估,小服务资源的浪费等问题逐渐显现,此时需增加一个调度中心基于访问压力实时管理集群容量,提高集群利用率。此时,用于提高机器利用率的资源调度和治理中心(SOA)是关键。