《大型网站技术架构》笔记(二)
前言
加油
正文
瞬时响应: 网站的高性能架构
A网站在100并发的时候, 接口响应时间是1s, 200并发时是10s
B网站在100并发时是1.5s, 200并发是还是1.5s
这种情况下, 用户认为哪个网站访问快呢?
网站性能是客观的指标, 可以具体到响应时间/吞吐量等技术可以衡量的技术指标, 同时也是使用者的主观感受, 感受这个东西就比较微妙, 不好用技术指标来统计
网站性能测试
性能测试是性能优化的前提和基础, 也是性能优化结果的检查的度量标准. 不同视角下的网站性能有不同标准和优化手段
用户视角
从用户角度, 网站性能指用户在浏览器上直观的感受慢还是快, 那么, 用户感受到的时间, 其实流程是: 用户点击鼠标 > 计算机浏览器解析指令 > 找到需要发生的请求 - 发起请求 > 网络通信 > 服务器端接收到请求 > 返回结果 > 网络通信 > 浏览器接收到结果 > 解析结果 > 展示到浏览器上
所以, 不同的浏览器/不同的计算机/不同的网络情况 可能导致用户感受到的延迟远远大于服务器请求的耗时.
在实际情况, 考虑使用前端手段优化速度, 比如
- 优化HTML样式
- 利用浏览器缓存
- 使用CDN/反向代理
目的是使浏览器更快显示用户感兴趣的内容, 尽可能近的获取页面内容, 即使不优化程序和架构, 也可以很大程度改善用户视角下的网站性能
开发人员视角
开发人员主要考虑程序本身及相关子系统的性能, 包括 响应延迟/系统吞吐量/并发处理能力/系统稳定性 等技术指标.
在实际情况, 考虑使用技术方面的优化手段, 比如
- 使用缓存加速数据读取
- 使用集群提高吞吐
- 使用异步加快请求响应与实现削峰
- 使用代码优化手段
运维人员视角
运维人员更关心基础设施的性能和资源利用率, 比如网络带宽/服务器硬件配置/数据中心网络架构/服务器/资源利用率
优化手段有:
- 建设骨干网络提高带宽
- 使用高性价比服务器
- 利用虚拟化技术提高资源利用率
性能测试指标
不同视角下有不同的性能标准, 不同标准有不同的指标
响应时间
一般指执行一个操作需要的时间, 从请求发送到收到最后的响应需要的时间, 是重要的指标, 直接反映系统的‘快慢’
测试响应时间需要请求很多次, 比如一万次, 计算其平均值
并发数
指系统同时处理请求的数目, 可反映系统的负载特性
测试并发数时一般通过多线程模拟多个用户来模拟用户进行操作, 为了更加真实, 通常在两个请求间加入一个随机的等待时间, 这个时间被称作思考时间
吞吐量
指单位时间内系统处理的请求数量, 体现系统的整体处理能力. 常用的量化指标有:
- TPS-每秒事务数-事务指一系列正常操作(最常用)
- HPS-每秒HTTP请求数-并发
- QPS-每秒查询数
在系统并发数由小到大的过程中. 服务器资源消耗也逐渐增大, 一般的, 系统吞吐量应该先逐渐增加, 到一定极限后继续增加会导致吞吐量下降, 然后到达崩溃点后资源耗尽, 吞吐量为0
对应的, 响应时间先是小幅度的上升, 到达吞吐量极限后快速的上升, 到达崩溃点后失去响应
网站优化需要尽量提高系统吞吐量, 最大限度利用服务器资源
性能计数器
是描述服务器和操作系统性能的一些指标, 包括 线程数量/内存使用/CPU使用/网络IO/磁盘占用 等指标, 当发现指标超过预定的报警阀值时应及时联系运维与开发人员处理
性能测试方法
性能测试
以系统设计时的性能指标为目标, 对系统不断施加压力, 验证系统在资源的可接受范围内是否能达到性能预期
负载测试
向系统不断的增加并发请求来增加系统压力, 一直到系统的某项或者多项性能指标达到安全临界值, 安全临界值的表现为超过该值之后系统处理能力会下降
压力测试
超过安全负载的情况下继续对系统施加压力, 直到系统崩溃
稳定性测试
被测试的系统在指定的 硬件/软件/网络环境 下, 给系统加载一定的业务压力, 使系统运行较长时间来观测系统是否稳定
性能优化策略
问题分析
如果有用户上报网站使用起来‘慢’, 必须对请求经历的各个环节进行排查, 定位问题
必须检查请求处理的各个环节的日志, 分析响应时间, 检查监控数据, 分析问题是代码问题还是硬件或者网络问题
Web前端优化
一般指网站业务逻辑前的部分, 比如: 浏览器加载/网站视图模型/图片服务/CDN服务 等
- 减少HTTP请求 - HTTP是无状态的应用层协议, 每次HTTP都要去建立链接和传输, 所以减少HTTP请求数量可提高性能, 比如合并 CSS/JS/图片 等资源
- 使用浏览器缓存 - 一般的, CSS/JS/ICON 等静态资源文件更新频率较慢, 将这些文件缓存在浏览器中可以很大的改善性能. 可以设置 HTTP 头的 Cache-Control 和 Exprise 属性来控制. 还要保证在发生更改时需要及时同步到用户的客户端中(不使用缓存), 可放置一个新的JS并更改主页面的HTML的JS引用将其引用到新的JS中, 需要更新N个JS时, 最好一次更新几个或一个避免访问量激增造成的网络堵塞
- 启用压缩 - 对文件(JS/CSS/HTML...)进行压缩, 在浏览器端进行解压缩可减少网络传输时间, 但是会对服务器和浏览器产生一定的压力
- CSS放在页面最上面, JS放在页面最下面 - 浏览器在下载完CSS才会对页面进行渲染, 所以将CSS放置在页面最上方让浏览器尽快下载, JS在加载后立刻执行, 有可能对页面进行阻塞, 造成页面显示缓慢, 因此JS放在页面最下面. 例外是页面解析需要使用JS, 那么就不要放置在底部
- 减少Cookie传播 - Cookie一般保存一些用户的信息, 有可能比较大, 会对数据的传输造成负担, 因此要对写入Cookie的数据进行慎重考虑, 同时为了避免访问静态资源时也携带Cookie造成无意义的损耗, 可将静态资源设置成独立域名避免请求静态资源时发生Cookie
CDN加速
CDN(内容分发网络)的本质是一个缓存, 将缓存放置在离用户最近的地方(物理), 使用户最快的获取数据
CDN部署在网络运营商的机房, 运营商又是终端用户的网络服务提供商, 因此用户请求数据会先到达CDN服务器, 当CDN存在用户需要的数据时直接从CDN返回, 这样可以加快用户访问速度, 同时减少数据中心的负载
CDN一般缓存静态资源, 比如图片/文件/css/JS等, 将其缓存到CDN会极大改善网页速度
反向代理
用户的请求发送到反向代理服务器, 反向代理服务器再转发到内部的服务, 因此, 反向代理服务器也可以保护网站的安全, 来自于互联网的请求必须经过代理服务器, 相当于在Web服务器和网络之间建立了一堵墙, 现在一般使用 Nginx 作为反向代理的服务, 可以完成多个操作, 比如
- 使用缓存 - 用户第一次访问静态资源时将该资源缓存到反向代理服务器中, 另一位用户访问时直接从代理服务器返回, 也可以将弱动态数据(博客/帖子/词条等一些实时性不强的数据)存储在缓存中, 当有数据变化时内部通知反向代理服务器将该缓存失效
- 负载均衡 - 如某个功能有多台服务器处理, 可将请求按照规则转发到某一台服务器, 提高系统处理能力和性能
应用服务器性能优化
分布式缓存
缓存一般指将数据存储在访问速度相对较高的存储介质中供系统使用, 缓存的访问速度快, 可以减少数据的访问时间, 另一方面如果缓存的数据是经过计算得到的, 那么该数据无需每次重新计算即可直接使用, 还起到了减少计算时间的作用
但是以下几种情况不适合使用缓存
- 频繁修改的数据 - 一般来说数据的读写比在2:1以上才需要加入缓存
- 没有热点的访问 - 访问量不大的数据没必要放置进缓存
- 数据不一致/脏读 - 缓存有过期时间, 有可能会出现缓存还没有过期时实际的数据已经更新的情况, 所以缓存的过期时间需根据实际情况确定, 有一种策略是数据更新时立即更新缓存, 这会带来更多系统开销和事务一致性的问题
缓存可用性
缓存的目的是提高数据读取性能, 理论上讲, 缓存数据的丢失或者不可用不会影响到程序的处理
但是随着业务的发展, 缓存会承担大部分的数据访问压力, 此时数据库的架构有可能会更多的依赖缓存, 如果缓存服务崩溃, 数据从数据库读取, 数据库可能会因为巨大的压力而宕机, 这种一般叫做缓存雪崩
为避免这种情况, 有的方法是对缓存进行热备, 但是这种设计本末倒置, 缓存本来就不应该当作可靠的数据源使用
比较好的方法是建立分布式缓存服务集群, 将缓存数据分布式存储. 就算其中一台宕机也不会对数据库产生多大压力
产品在设计之初就需要一个明确的定位, 什么产品实现什么功能, 什么不是产品的特征, 在产品的生命周期中, 会有很多困难和诱惑来修改产品的发展方向. 左右摇摆/什么都想做 的产品, 最后很有可能会成为一个没有生命力的四不像
缓存预热
缓存中存放热点数据, 热点数据是缓存系统使用LRU(最近最久未使用算法)对数据筛选淘汰出来的, 这个过程时间较久, 新启动的缓存系统在重建缓存数据的过程中, 对性能和数据库的提升都不高, 所以最好在缓存系统启动时把热点数据加载好, 这种手段叫 缓存预热, 比如 省市区三级联动/类目信息 等, 可以在缓存系统启动时加载到系统中
缓存穿透
对于不恰当的业务, 或者遭受到了恶意的攻击, 持续的高并发的请求某个不存在的数据, 缓存没有获取到这个不存在的数据, 那么会到数据库检索, 导致数据库压力大, 一般的对策是在这个频繁访问的数据到达阀值后将该数据也加入缓存, 其Value为Null
Memcached
Memcached 是非常出名的分布式缓存框架, 被大量网站使用. 他的优点是 设计简单/服务器集群间互不通信/海量数据可伸缩
通信协议
Memcached使用TCP协议进行通信, 其序列化协议是基于文本的自定义协议, 非常简单, 大概是以一个命令关键字开头, 后面是命令操作
丰富的客户端
因其通信协议很简单, 因此主流的语言都有对应的客户端, 开发很方便
高性能的网络通信
服务端的通信模块基于 Libevent, Libevent是一个支持事件触发的网络通信库(C), 其有 稳定/轻量/快速 等特点
高效的内存管理
使用固定内存分配, 避免了内存碎片管理的问题
集群架构互不通信
其分布式架构, 多台节点之间互不通信, 由客户端分布式算法来确定缓存存放在哪个节点, 取时根据key确定存储机器再进行获取, 我们可以将其比喻成一个圆, 有两个节点就相当于将这个圆分两半, 通过客户端hash一致性算法计算出来key是哪一个区间. 当添加或者减少某个节点, 也只影响这个节点相关的一个节点的数据
异步操作
任何可以晚点做的事情都应该晚点再做
异步一般搭配消息队列, 不仅可改善网站的扩展性. 还可以改善网站的性能. 需要注意的是, 用户在接收到返回时可能只是入了队列还未执行完成, 因此业务要相应的进行调整
使用集群
将业务搭载在集群中, 由负载均衡服务进行任务的分配, 比如我有三台机器进行负载均衡, 那么每台机器的压力则为全部的 3/1, 能有效提高效率和减少负载
代码优化
多线程
多线程带来的是效率的提升, 但是随之而来的是BUG的难以调试和线程安全问题, 线程安全的解决一般如下
- 将对象设计为无状态
- 使用局部对象
- 并发访问资源时使用锁
资源复用
系统运行时, 一定要减少开销很大的系统资源的创建和销毁, 比如 数据库的连接/网络通信连接/线程/复杂对象 等, 资源复用有两种模式
- 单例模式
- 对象池
数据结构
pass
垃圾回收
现在好多语言自己处理垃圾回收
存储性能优化
- 使用SSD(固态硬盘), 但是成本会提高