我们完成了系统的拆分,做好了负载均衡,并完成了配置中心。在请求量不太大的情况下,我们其实已经完成了系统的优化。等到后期业务继续扩张时,我们遇到的瓶颈就不再是系统,而是数据库了。那么要如何解决这个问题呢?
第一种方式是主从复制与读写分离。读写分离可以解决数据读写全都在一个库上的问题,通过将主从库拆分为 master 和 slave,让写这一环节全部由 master 来处理,将写的压力分摊从而提高数据库性能。之后随着业务量的继续增大,单独的主从复制已经无法满足我们的需求时,我们通过第二种方式来处理。
第二种方式是进行垂直拆分。垂直拆分的概念和业务的拆分相似,我们根据服务将数据库拆分为 Users、Orders、Apps 等等,让每一个服务都拥有自己的数据库,避免统一请求从而提升并发性。伴随业务量的继续增长,即便是单独的库也会到达瓶颈,这时我们就需要用到第三种方式。
第三种方式是水平拆分。比如我们将 Users 这个数据库内的表进一步拆分为 Users1,Users2,Users3 等等多个表。要完成这个拆分我们需要考虑,面对多个表我们在查询时要如何去做的问题。这时我们需要按照我们的具体业务来判断。比如查询用户,我们可以根据用户 ID,将 ID 拆分分片,然后使用哈希算法让他们统一在一定范围内。之后我们每次拿到 Users 就通过哈希来计算具体在哪一片并快速抵达相应位置。Auting 多租户的设计就用到了拆分的概念,如下图所示。
服务限流
等到业务量多到一定程度后我们肯定会涉及到服务限流,这是一个变相的降级策略。虽然我们的理想都是系统能够承受越来越多的用户越来越多的量,但是因为资源总是有限的,所以你必须要进行限制。
请求拒绝
服务限流有两种主要算法,漏桶算法与令牌桶算法。我们可以看一下上图,它画的比较形象。漏桶算法中我们可以将流量想象成一杯水,在水流流出的地方进行限制,无论水流流入的速度有多快,但是流出速度是一样的。令牌桶则是建立一个发放令牌的任务,让每一个请求进入前都需要先拿到令牌,如果请求速度过快令牌不够用时就采取对应的限流策略。除去这两种算法,一般还会用到大家都很熟悉的计数器算法,感兴趣的朋友也可以去自行了解一下,这里我们就不细谈了。
这几种算法其实本质上都是在流量过量的时候,拒绝过量的部分的请求。而除去这种拒绝式的策略,我们还有一种排队的策略。
消息队列
当我们的业务有无法限流、拒绝的情况存在时,我们就需要用到队列消息。
如图所示,消息队列的主要概念是生产者会将消息放入队列中,由消费者从队列中获取消息并解决。我们通常使用 MQ、Redis、Kafka 来做消息队列。队列负责解决发布/订阅和客户端推拉两个问题,生产者负责解决以下问题:
- 缓冲:为入口处过大的流量设置缓冲
- 削峰:与缓冲的效果类似
- 系统解耦:如果两个服务没有依赖调用关系,可以通过消息队列进行解耦
- 异步通信
- 扩展:基于消息队列可以做很多监听者进行监听
服务熔断
在业务正常提供服务时,我们可能会遇到下图这种情况:
服务 A、B 分别调用服务 C、D,而这两者则都会调用服务 E,一旦服务 E 挂掉就会因为请求堆积而拖垮前面的全部服务。这个现象我们一般称之为服务雪崩。
而为了避免这个情况的发生,我们引入了服务熔断的概念,让它起到一个保险丝的作用。当服务 E 的失败量到达一定程度后,下一个请求就不会让服务 E 继续处理,而是直接返回失败信息,避免继续调用服务 E 的请求堆积。
简单来讲这是一种服务降级,通常的服务降级还有以下几种:
- 页面降级:可视化界面禁用点击按钮、调整静态页面
- 延迟服务:如定时任务延迟处理、消息入 MQ 后延迟处理
- 写降级:直接禁止相关写操作的服务请求
- 读降级:直接禁止相关读的服务请求
- 缓存降级:使用缓存方式来降级部分读频繁的服务接口
- 停服务:关闭不重要的功能,为核心服务让出资源
压测
上图就是我们具体压测要关注的东西。首先我们要知道压测其实是一个闭环,因为我们可能会需要重复这个流程很多次,不断地重复发现问题、解决问题、验证是否生效、发现新问题这个过程,直到最终达到我们的压测目标。
在压测开始前我们会制定压测目标,然后依据目标来准备环境。压测模型可以是线上的,也可以是线下。一般线下考虑到成本问题,因此会选择单机或小集群来进行,这可能让结果不太精准,所以通常大家都选择在线上或者机房来进行压测,数据更精准。在压测过程中我们会发现新的问题,然后解决它,并验证结果直到达到压测目标。
在压测的过程中我们需要关注以下几点。首先是 QPS,即每秒查询量。它和 TPS 的区别在于,TPS 有事务的概念,需要完成事务才算一次请求。而 QPS 没有这个概念,它只要查询到结果就算做一次请求。其次是 RT(响应时间),这个需要我们重点关注,而且越是高并发的系统,RT 越重要。之后在压测中我们需要关注系统到底能承载多大的并发数和吞吐量。成功率则是指在压测过程中,当压力越来越大的时候我们的业务是否能按照原计划执行并得到既定结果。GC 则是指垃圾回收,这也是个很大的问题,因为如果我们代码写的不好,那么随着压力的增大 GC 逐渐频繁最终会导致系统停顿。
之后则是硬件方面,需要我们关注 CPU、内存、网络、I/O 的占有率,有一种任意一项卡主就有可能导致一个系统瓶颈。最后是数据库,这里暂不展开细讲。