系统性能优化策略 -- 持续优化更新

【开篇词】
     无论是传统行业还是互联网行业,一个优秀的软件产品,必须兼顾产品的质量和性能。这二者也可以反映出从业者的技术能力、思维方式以及格局!故性能优化应该是我们所有猿类共同的追求。


【策略】
      业界衡量一个软件系统性能的三个指标:TPS(Transactions Per Second),QPS(Query Per Second)以及RT(Response Time),其实前二者可以等价于写操作和读操作。如此说来,系统性能优化无非是提升读写的效率,对应到实际应用场景就是增加系统的吞吐量,最终对于用户的直观感受就是较低的响应时延。

      本文将以常见的B/S架构为例,从软件架构和业务流程两个角度着手阐述如何提升系统性能:
1. 软件架构
  性能优化贯穿于一个请求的整个链路,是前后端共同合力的结果。业界优良的解决方案最终形成了框架,框架之中自然包含了我们想要的答案。(当然了,框架解决的不仅仅是性能问题)
  关于前端,目前主流框架无外乎Angular、VUE、React,可以让你快速搭建一个高性能的Web服务,其完备的路由功能极大的简化了你开发的复杂度,其易扩展的特性,可以使得你利用第三方插件开发出具备更加优美外观和完善功能的Web服务。感兴趣的可以自行了解,本文不再赘述。至于为什么会出现如此多的前端开发框架?本人拙见是为了更好的分层设计、组件化、易扩展、易维护。
  关于后端,不同的开发语言有不同的框架,尤其是Java语言,其可选择的框架数不胜数。但针对性能优化这一需求,这些框架有以下共同特点:
1. 多线程/多进程
  我们所需要的服务是由程序提供的,而程序的承载体则是线程或者进程。故可以通过简单的增加线程或者进程的数量来提升性能。
2. 线程池/进程池
  多线程可以很好的实现资源共享,但是存在线程创建/销毁的资源消耗以及隔离性差的缺点;多进程虽然可以很好的实现隔离,但是为了实现资源共享,需要依赖进程间通信,一般而言,进程的资源消耗要远大于线程。
  为了平衡上述问题,线程池/进程池的概念出现了,结合实践经验,通过对当前业务容量的预评估,合理设计系统的线程数/进程数,在程序部署完成启动之时,创建好预设数量的线程/进程,使之常驻系统,来应对业务请求,避免了线程/进程的频繁的创建和销毁。但这样一来,需要一个完善的线程池/进程池资源管理机制,相应的引入一定的复杂度。
3. 分布式集群
  从业务兴起初期,单台服务器设备就可以支撑起整个业务请求,随着业务的发展,进一步的可以增加单台服务器的内存、磁盘容量,提升CPU核数和主频,甚至采用闪存来代替普通磁盘。所有这些应对措施的出发点都是本着提升系统的读写效率。
  针对一个内网系统,比如各种办公管理系统,考核系统,因为用户数量有限且一般不会出现集中式访问,故单台服务器足以,考虑到服务的高可用,我们考虑增加一台备机即可。
  互联网的发展大家有目共睹,这类系统动辄百万上亿级别日活,单台服务器及硬件资源扩充方案简直就是渣渣。其实不难,我们可以对上述方案进行横向扩展,通过增加服务节点来达到性能扩展的目的。乍一看这个思路没问题!但如果为了达到特定的性能指标,你需要增加的是成百上千台服务器呢?你确定还会沿用这个思路吗?
  所以说,通过简单增加机器,镜像部署服务是治标不治本,且付出的代价是巨大的,那么我们能否通过重新审视现有的软件架构以及业务逻辑来挖掘出潜在的内因呢?答案是肯定的,大致可以从以下几个角度进行分析:
    a. 分层与解耦
    b. 使用缓存
    c. 读写分离
    d. 负载均衡
    e. 区分核心业务与非核心业务


2. 业务流程
     众所周知,一个Http(POST)请求的完整路径如下:
     表单数据提交 -> 浏览器发送Post请求 ->服务端解析Post请求 -> 服务端执行业务逻辑组装Response数据 ->服务端发送响应给浏览器 -> 浏览器解析返回的Html数据 -> 浏览器页面渲染请求其他资源(CSS/JS/图片链接等)。
系统性能优化策略 -- 持续优化更新_第1张图片

  • 单次请求
    在这个过程中我们需要注意以下三点:
      1. Request请求Body与Response响应Body的数据应该尽可能少
          在客户端和服务端之间往返传输较大量级的数据不仅需要耗费较多的带宽,也会占用更多的CPU资源(数据的加解密/编码/运算),也就在某种程度上影响了系统的性能。此外,将数据体保持在一个合适的大小,在一定程度上与博文《谈谈代码重构》中接口设计的“单一职责”原则吻合——一个接口应该仅仅实现某一个特定功能。
          为了减小单次请求所携带的数据体与请求应答所返回的数据体的量级,需要合理设计请求和应答所需要的数据,避免无用数据被传输,可以考虑避免向服务器请求不经常变动的数据(静态数据,包括页面渲染时依赖的CSS样式、JS脚本、图片等)。
      2. 请求链路应该尽可能短/依赖尽可能少
          一个完整的请求链路如上图所示,始于一个POST请求的发起,终于一个Response应答的接收。这条链路的时延取决于服务端的业务逻辑,一个简单的业务逻辑可能仅仅是一次数据库的读(HTTP-GET)或者写(HTTP-POST/UPDATE/DELETE)操作,中间不存在任何计算环节或者其他依赖。相反,一个复杂的业务逻辑难免需要进行复杂的逻辑运算或者依赖于其他业务的中间结果,此时,请求的链路势必增加,请求的依赖也极可能增加。
          在单体应用中,一次请求所依赖的业务逻辑最复杂的情况是需要本地不同进程间通信协作完成;但对于一个分布式系统而言,一个请求所依赖的业务服务是分开部署的,此时业务服务之间的协作将会采用远程过程调用RPC来完成。如此,一次请求除去链路增加,依赖增加之外,也更容易受到网络状况的影响,最终请求时延必然有所增加,随之而来的负面效应是用户体验下降。
          为了缩短请求链路,可以考虑合理利用缓存(服务端缓存/CDN缓存/客户端缓存);为了减少依赖,需要合理进行接口设计——高内聚低耦合。
      3. Response的Body中应该尽可能减少额外的依赖
          这一点算是客户端性能优化的策略之一,更好的考虑用户体验。在页面渲染的过程中,因为过多的外部依赖将导致页面数据加载缓慢,影响用户体验。如何减少额外请求呢?将多个外部请求合并为一次请求或者采用缓存策略来保存部分不常变更的数据到距离用户最近的地方。
  • 并发请求
      1. 将不存在顺序依赖的多个请求合并为单次请求
          前端开发中,某一页面数据经常依赖于多个后端数据的组合结果,若多个后端数据的获取没有严格的顺序依赖,则可以将这些后端请求合并为一个请求,一次性发送到后端。在JS开发中,我们可以采用Promise对象来实现这一优化,具体用法可以参考:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise/all
      2. 限制最大并发请求量
    服务端应该采取某种措施来限定并发请求的数量,通过设置一个合理的阈值,来避免服务端资源被过多的并发连接消耗;

你可能感兴趣的:(系统架构,后端开发)