跳到主要内容

流量还在涨:所有服务都部署在一台机器上

在想到一个好点子后,终于找到程序员小D把项目开发完成、上线了。看看整体情况:

  • 一台服务器,LNMP架构

单机模式是最初始的状态

注:LNMP 指的是 Linux + Nginx + MySQL + PHP,是早期的非常流行的Web服务选型

流量还在涨:单台机器有压力了

流量还在涨,这时候单台机器开始报警了:内存、磁盘、CPU 都有点扛不住了。

小D发现 MySQL 服务占用了不少资源,把它放到单独的服务器上部署会好一点。于是进行了第一次调整:

  • 两台服务器:L + M 和 L + N + P

业务服务和基础服务拆开 是比较早期的运维手段

流量还在涨:MySQL服务扛不住了

流量还在涨,MySQL被请求过多,扛不住了。

小D想,MySQL服务很重要,该省省该花花,可以上好点的服务器啊。于是将MySQL的服务器配置调高好几个档次。

纵向扩容 提高单机的能力是最简单有效的手段。钞能力有超能力

流量还在涨:MySQL服务又扛不住了

小D分析了一下业务现状,PHP业务里面做了很多很重的查询,但是很多内容其实都是静态的。

那将这些内容缓存到内存中,不要去服务数据库就好了啊。

内存缓存可以极大的提高访问速度、减少存储系统的压力

注:内存缓存有多种算法,比如:

  • LRU:淘汰的是最长时间没有被使用的数据> 经常被使用
  • LFU:淘汰的是最少次被使用的数据
  • ARC:这个算法平衡了 LRU 和 LFU > TODO 详细说明

流量还在涨:Web服务的磁盘报警了

现有的业务实现,所有的图片文件都直接保持在Web服务当前的文件夹下面。数据一多,磁盘就满了。

小D将静态资源单独的存储到一台IO密集型的机器上,它有超大的磁盘空间,而且还不贵。

静态资源单独存放 极具性价比

流量还在涨:Web服务CPU报警了

用户量是缓慢上涨的,上面的事情按部就班的处理了也就好了。Web服务的CPU扛不住了,小D想着这次就不要纵向扩容了,因为好东西确实贵,而且按照这趋势,顶配的配置也很快会扛不住,于是他选择了多部署Web服务器,将Web服务器的IP加入到域名中。

他发现通过前期的数据库改造、文件内容改造,Web服务已经是无状态的了。更多的部署轻而易举。

横向扩容:通过增加服务器的个数,可以低成本无上限的扩展能力

流量还在涨:新来的程序员只会写前端

小D一开始用的是 PHP 的模板,从数据库找时间后渲染成HTML返回。一个人忙不过来了就招了个前端小F。前端表示:

  • 两个人在一个仓库里面同时改代码比较麻烦
  • 他也不想学模板的语法,不好调样式啥的

小D也发现为了同时为Web端和手机端提供服务,现在的实现方案确实也有问题:

  • 服务端逻辑有重复
  • 服务端还要根据客户端的类型写多个模板

于是两人一拍即合:

  • 服务端对外通过Restful API,供各个端调用
  • 客户端调用服务端的API,自己写前端内容实现赏心悦目的交互体验

前后端分离 将程序员的职责区分开来,让具体的事情被专业的人做到极致

流量还在涨:文件服务器报警了

文件服务器除了存储了用户数据,还存储了前端需要的 html、css 文件等。 通用的文件比如 jQuery.js 可以白嫖公开的CDN,自己的 html 什么的就需要用相同的思路处理一下了。

小D让小F将自己的开发编译后的产物放到CDN上,更快更便宜啊。这个事情需要前端:

  • 将写在HTML文件里面的css和js放到外部文件
  • 调整现有的 css和js 的引用路径

文件服务也是一个具体的基础服务,CDN 能够低成本的让用户实现就近访问

流量还在涨:用户抱怨访问失败的时间有点长

小D收到用户吐槽,网站打不开了。小D自己没有复现,但是经过分析后找到了原因:有服务器宕机了,但是DNS系统始终没有更新。 小D也感觉不合理,每次水平扩容都需要购买新的IP,修改DNS的解析记录;有服务宕机了也需要去修改DNS的解析记录。随着实例越来越多,这些事情发生的概率也越来越高。 于是,他搭建了一个负载均衡集群,专门进行流量的分发。所有的请求先到负载均衡集群,再交给Web服务处理。

负载均衡 是服务集群化的实施要素,可以将负载分摊熬多个操作单元上执行

流量还在涨:数据库读压力太大了

Web实例越来越多,数据库的读压力也是越来越大了。是时候上数据库读写分离了。小D给数据库按照了几个从库,它们同步主库的数据,只对外提供数据查询服务。应用程序在查询的时候,只访问从库。

数据库读写分离是处理数据库读压力的常规手段

流量还在涨:数据库读压力太大了2

Web实例越来越多,数据库的读压力也是越来越大了。是时候上多级缓存了。 小D使用了Redis,作为一级> 内存缓存的补充,充当二级缓存。

多级缓存在不同的场景有不同的实现,比如静态资源会有 客户端本地缓存 -> 客户端内存缓存 -> CDN -> OSS, 动态资源有内存缓存 -> Redis缓存 -> 数据库

注:只要用到了缓存,都会有数据一致性的问题,需要考虑缓存过期/更新策略。一般的方案有:

  • Cache Aside模式:程序自行维护。在更新后删除缓存
  • 使用中间件去维护缓存:数据库和缓存的维护都交给缓存代理,应用程序将缓存代理当做唯一的数据源
  • 基于MySQL的binlog异步删除缓存

流量还在涨:数据库读压力太大了3

在一些场景下,数据库被打挂了:

  • 缓存雪崩:大量的Key集体过期,大量请求请求到数据库
  • 缓存击穿:某个热点数据过期后、热点数据重新载入缓存前, 大量请求请求到数据库
  • 缓存穿透:访问到的是一个不存在的记录

问题分析是困难的,问题处理则很简单:

  • 缓存有效期加上随机值
  • 热点数据永不过期
  • 逻辑过期与异步更新:设置一个逻辑过期时间T1 和 物理过期时间 T2,在T1到T2期间程序异步更新缓存,同步返回缓存
  • 布隆过滤器可以确认某个值是不是不存在> TODO 说明细节

缓存在使用过程中需要特别关注过期策略

流量还在涨:新实例启动会让整体服务不稳定

每次新实例启动时,因为缓存数据为空,都会让系统有个波动。小D需要处理这个问题:缓存预热。手段大概有:

  • 应用程序启动时自己请求需要的数据
  • 预热系统通过访问应用程序让应用程序完成数据缓存

缓存预热也是缓存使用过程中常用的实践。

流量还在涨:程序开发、上线冲突

业务真是壮大了,也陆陆续续有新的员工加入,分别负责不同的子模块。但是每次上线光是合代码就很痛苦。 小D和大家讨论,要不大家拆开吧,自己维护自己的。各服务有自己明确的系统边界,服务之间通过远程调用通信。

业务复杂后,无论是为了满足业务架构还是组织机构,单体应用拆分为微服务都势在必行

注:这一步实际上也完成了数据库拆库。

流量还在涨:数据库压力太大了

拆分了项目后,小D负责的模块也被单独的拆分到了自己的数据库实例上。这一步完成了垂直分库:每个业务将自己的表放入到自己的库中,不同的业务的库不一样。

但是业务确实很壮大,数据库又扛不住了:单表的数据太大,写入和查询都很慢 小D做了如下的调整:

  • 垂直分表:将一张表拆分为多张表,比如商品表拆分为商品摘要表和商品描述表,不同的场景查询/更新的表不同
  • 水平分表:将同一张表的数据分散到同一个库的不同的表中,减少单表的数据量(可以按主键hash,也可以按照用途,比如历史冷数据,近一个月热数据拆分)
  • 水平分库:将数据物理的分散到不同的存储实例上,减少单库的数据量

分库分表是解决海量数据的常规手段

流量还在涨:特殊时期资源不够用

运营说在一月一号这一天给所有的用户打折,预计流量会涨3倍。 小D表示系统扛不住,需要加机器 财务表示没有钱,而且后期机器都闲置了 小D问运营:那把评论、退货服务关一天,可以接受不? 运营咬咬牙,同意了。

服务降级没有统一的标准,需要具体情况具体分析。

  • 核心服务通常不可降级的,非核心服务可能能部分降级
  • 服务内部可以通过减少日志输出来完成降级
  • 降级的执行可以是事先的,也可以是应急的;可以是自动的也可以是手动的

流量还在涨:非核心服务导致了系统崩溃

小D负责的系统出了一个大Case:这个系统依赖了一个小的服务,平时没有什么问题,但是那次不能响应了,导致他负责的系统整体耗时、资源使用都飙升。 通过分析,小D认为其实对方不响应也问题不大,给个近似值也可以的。于是开始了系统改造:引入熔断。

熔断机制为:统计被依赖的服务的可用性,如果不达标,一段时间内就不访问它了,使用预设的 fallback 逻辑;服务恢复了就继续访问。

熔断是将局部故障隔离、不影响系统其他服务的常规手段

流量还在涨:为什么有业务方不顾我的死活疯狂请求

小D又遇到一个服务调用方,它不知道为什么,在疯狂的请求小D负责的业务,导致集群差点崩溃了。 小D想着还是要自我保护一下,毕竟对其他的服务都有SLA承诺的,他加上了限流:对于单实例有上限,超了直接返回500;对于每个业务方,约定好配额,超了也返回500。

限流是自我保护的重要手段

流量还在涨:...

流量永不休,本文完。