架构设计思考-2

架构设计原则

架构设计需要遵循的三大原则:合适原则、简单原则、演化原则

合适原则

合适优于业界领先

举个栗子,几个人规模的团队想做一个类似QQ的“亿级用户平台”,最终会导致整个项目的开发和后续的迭代成为灾难,主要有三个原因:

  1. 没那么多人,却想干那么多活,是失败的第一个主要原因
  2. 没有那么多积累,却想一步登天,是失败的第二个主要原因
  3. 没有那么卓越的业务场景,却幻想灵光一闪成为天才,是失败的第三个主要原因

真正优秀的架构都是在企业当前人力、条件、业务等各种约束下设计出来的,能够合理地将资源整合在一起并发挥出最大功效,满足当前的业务场景并能够快速落地

简单原则

简单优于复杂

总的来说,架构应该在满足业务场景及可扩展性前提下,尽可能简单地设计;软件领域的复杂性主要体现在两个方面:

  1. 结构复杂:结构复杂的特点是组成系统的组件更多,同时组件之间的依赖关系更加复杂,存在两个问题:
    • 组件越多,就越有可能因为某个组件的异常导致系统不可用
    • 某个组件改动,会影响关联的所有组件
  2. 逻辑复杂:减少组件复杂性,会带来业务逻辑的复杂,存在以下问题:
    • 系统庞大,维护困难
    • 几十上百人维护同一套代码,开发、测试、部署会异常混乱
    • 故障排查困难,很难定位具体问题
    • 。。。等等

因此,架构设计时如果一个简单的方案和复杂的方案都可以满足需求,一定要选简单的方案。KISS原则:Keep It Simple, Stupid!

演化原则

演化优于一步到位

软件“架构”的概念源自于建筑的“架构”,但是两者存在一个根本的区别:对于建筑来说,永恒是主题;而对于软件来说,变化才是主题!!!

软件结构需要根据业务的发展而不断变化。所以,想一步到位设计一个软件系统是不可取的。软件架构设计的过程:

  1. 首先,设计出来的架构要满足当时的业务需要
  2. 其次,架构要不断地在实际应用过程中迭代,保留优秀的设计、修复有缺陷的设计、改正错误的设计、去掉无用的设计,使得架构逐渐完善
  3. 最后,当业务发生变化时,架构要扩展、重构、甚至重写;代码也许会重写,但有价值的经验、教训、逻辑、设计等却可以在新架构中完善

架构设计流程

有的放矢:识别复杂度

架构设计本质上也是为了解决系统问题,而解决问题的第一步应该是了解问题。对于架构设计而言,了解问题即了解系统复杂度

按图索骥:设计备选方案

架构设计时,架构师需要将视野放宽,考虑更多可能性。因此在架构设计中有三个常见错误:

  1. 设计最优秀的方案:根据前面说到的合适原则和简单原则,方案并非越“优秀”越好
  2. 只做一个方案:架构师一般会有自己的倾向,解决一个问题可能也能想到多个解决方案,但是只因为自己的倾向就做简单的决策而仅得到一个方案,会出现一些问题:
    • 心理评估过于简单,可能没有考虑全面。可能某个方案因为自己想到的某个缺点就否决了,但是事实上所有的方案都不是完美的,留下的方案可能也有缺点而自己不自知。
    • 架构师可能评估的标准和方向并不正确
    • 单一方案设计会出现过度辩护的情况,即在架构评审时,针对方案的问题和疑问,架构师会竭尽全力去辩护,甚至强词夺理;
  3. 备选方案过于详细:将注意力集中到细节中而忽略了整体的技术设计,可能会导致备选方案间的差异不大,且不核心;正确的做法是备选阶段关注技术选型,而不是技术细节

深思熟虑:评估和选择备选方案

每种方案都是可行的,每种方案都不是完美的,那怎样选出最优的解决方案呢?

  1. 最简派?选择最简单的方案
  2. 最牛派?选择最牛最复杂的技术
  3. 最熟派?架构师自己对哪块技术比较熟,就使用哪个技术方案
  4. 领导派?给出几种备选方案,让领导做决定

上面几种“派别”不能说对错,各有各自的使用场景。对于通用的架构设计来说,评估方案的具体方式建议为:列出我们需要关注的质量属性点,然后分别从这些质量属性的维度去评估每个方案,再综合挑选适合当时场景的最优方案。(可以考虑“360度环评”)

计算高性能

单服务器场景下的高性能

单服务器性能关键之一即是网络模型,网络模型编程有两个关键设计点:

  1. 服务器是如何管理连接的?
  2. 服务器是如何处理请求的?

以上两个设计点最终都和操作系统的I/O模型有关及进程模型有关

  1. I/O模型:阻塞、非阻塞、同步、异步
  2. 进程模型:单进程、多进程、多线程

PPC

ppc即Process per Connection,其含义是每次有新的连接就新建一个进程来处理,是传统的UNIX网络服务器所采用的模型,其流程如下:

  1. 父进程监听并接收连接
  2. 父进程fock子进程
  3. 子进程处理连接的读写请求(包括read、业务处理、write)
  4. 子进程关闭连接(子进程close)

存在的问题:

  1. fock代价高,需要分配很多内核资源,需要将内存映像从父进程复制到子进程
  2. 父子进程通信复杂:需要采用IPC之类的进程通信机制
  3. 对于多请求的系统来说,进程处理时间稍久就会造成进程堆积,从而产生大量进程调度和切换成本

prefock

pre-fock即提前创建子进程,不用在请求来的时候再fock,可以降低请求的时间消耗,但是PPC的核心问题如IPC何进程调度切换成本问题不能解决

TPC

tpc即Thread per Connection,其含义是每次有新的连接时就新建一个线程来处理。与进程相比,线程更加轻量,创建线程的消耗和线程切换的代价更小,同时线程间通信比进程间通信更加简单;TPC的基本流程如下:

  1. 父进程监听并接收连接
  2. 父进程创建子线程
  3. 子线程处理连接的读写请求(子线程read、业务处理、write)
  4. 子线程关闭连接

TPC解决了(或者弱化了)PPC中的进程fock代价高和父子进程通信复杂的问题。但是也引入了其他问题:

  1. 线程间共享内存空间,共享资源的处理需要加锁,甚至导致死锁问题
  2. 线程间可能互相影响,某个线程异常时,可能导致整个进程退出

prethread

和prefock类似,预先创建线程,可以减少新连接到来时新建线程的时间消耗

Reactor

不管是PPC还是TPC,都有一个问题即每次请求来之后,进程/线程都需要同步去read,如果当前连接没有数据可读,则进程/线程会处于阻塞状态,这种阻塞状态会使进程/线程的使用率更低,因此出现了I/O多路复用

I/O多路复用中“多路”就是指多条连接,“复用”指多条连接使用同一个阻塞对象,如select中的阻塞对象是fd_set, epoll中阻塞对象为epoll_creat创建的文件描述符

I/O多路复用技术的两个关键实现点:

  1. 当多条连接共用一个阻塞对象后,进程只需要在一个阻塞对象上等待,而无需再轮询所有连接
  2. 当某条连接有新的数据可以处理时,操作系统会通知进程,进程从阻塞状态返回,开始进行业务处理

I/O多路复用结合线程池,即Reactor模式; Reactor模式又叫“Dispatcher模式”,即I/O多路复用统一监听事件,收到事件后分配(dispatch)给某个进程;

Reactor模式的核心组成部分包括Reactor和资源处理池(进程池或者线程池),其中Reactor负责监听和分配事件,资源处理池负责处理事件

单Reactor单进程/线程
  1. Reactor对象通过select监控连接事件,收到事件后通过dispatch进行分发
  2. 如果是连接建立事件,则通知Acceptor创建连接,并新建一个Handler,以备处理后续的各种事件
  3. 如果不是连接建立事件,则Reactor会调用第2步中创建的Handler来进行响应
  4. Handler会完成read -> 业务处理 -> send的完整业务流程

但Reactor单进程/线程的典型代表是Redis

单Reactor多线程

单Reactor多线程解决了单Reactor单进程/线程中不能利用多核CPU的问题,通过建立线程池的方式来处理业务逻辑,其过程大致如下:

  1. 主线程中,Reactor对象通过select监控连接事件,收到事件后进行分发
  2. 如果是连接建立事件,则由Acceptor处理,接受连接并创建对应的Handler
  3. 如果不是连接建立事件,则Reactor会调用第2步创建的Handler来进行响应(以上三步和单Reactor单进程比较相同)
  4. Handler只负责响应事件,不进行业务处理;Handler通过read读取到数据后,会发给Processor进行业务处理
  5. Processor会在独立的子线程中进行业务处理,并将结果发送给主线程的Handler
  6. Handler收到Processor的结果后,通过send将结果返回给Client

这里只有多线程而不用多进程的原因在于,如果采用多进程,子进程完成业务处理后将结果返回给父进程,并通知父进程发送给那个client会比较麻烦(应该还是进程间通信的复杂度?)

单Reactor多线程的问题在于Reactor承担所有事件的监听和响应,可能会成为性能瓶颈;二是多线程的数据共享和访问比较复杂。

多Reactor多进程/线程

通过引入多Reactor,可以解决单Reactor多线程的问题

  1. 父进程中的MainReactor对象通过select监控连接建立事件,收到事件后通过Acceptor接收,将新的连接分配给某个子进程
  2. 子进程的SubReactor负责将MainReactor分配的连接加入连接队列进行监听,并创建一个Handler用于后续事件处理
  3. 当有新的事件发生时,SubReactor会调用对应Handler响应,Handler在当前进程中完成Read -> 业务处理 -> send的整个处理流程

优点有如下几个:

  1. 父子Reactor分工明确,父Reactor只负责接收新连接,子Reactor负责后续事件处理(并发)
  2. 子Reactor所在进程完成整个业务处理,无需再返回数据给父进程

采用多Reactor多进程的软件代表为Nginx,采用多Reactor多线程的代表有Memcache和Netty

集群高性能

负载均衡分类

  1. DNS负载均衡: 通过DNS解析进行负载均衡
    • 优点:简单、成本低
    • 缺点:负载粒度太粗(地区级别),更新不及时(DNS缓存时间一般较长),扩展性不太好
  2. 硬件负载均衡:通过硬件设备实现负载均衡,代表F5和A10;贵
  3. 软件负责均衡:通过软件实现负载均衡,常见Nginx和LVS
    • Nginx是七层负载均衡,一般通过http中url不同,负载到不同的实例进行处理
    • LVS是四层负载均衡,主要在TCP层对不同的端口进行流量分发
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值