高并发系统落地与瓶颈突破

530 阅读20分钟

一缕风华 晚间充电

![](https://mmbiz.qpic.cn/mmbiz_png/8j8rOGKesI64FiafJTzWXOT00uAyhpBJxuCfnNCdPxV7H8IsS9g6uPpcFPlALWZLEdT2KYM2wXr7ichI7YEkcPTA/640?wx_fmt=png)

生活本来沉闷无味 但跑起来就有~

过去二十年来,编程语言得到飞速发展,作为软件的重中之重架构,它也在发生巨大的改变,本篇讲解了架构是如何演化而来、高并发系统常见痛点、如何治理分布式系统,系统瓶颈解决思路。祝您看完后,对架构理解更近一步,在面对瓶颈突破时也能拥有更多选择~

分布式系统架构演变

一般来说,系统由小变大的过程,几乎都要经历单机架构,集群架构,分布式架构。伴随着业务系统架构的还有关系型数据库存储架构的提升,分库分表,从本地缓存到分布式缓存。

![](https://mmbiz.qpic.cn/mmbiz_png/c8XbGB9esl9N3PYCKHkTYBicroEFZ4xEUnib5Cgk3T7MAPVNTkWMuqp1oGSBCIhMIcrrbMozR9P2b1pUN1buaicyw/640?wx_fmt=png)

任何一个网站诞生之初都不可能直接就拥有庞大的流量和海量数据,都是在不停的试错中演变自身架构,如果业务没有起色,一味的追求大型网站架构又有什么意义呢。

集群架构

一旦用户开始增加,并发流量上来了,为了让用户拥有更好的体验,我们不得不对单机架构进行调整和优化,因此在这个阶段主要需要解决的就是系统的并行能力。降低单机负载,支撑更多的用户访问操作。

![](https://mmbiz.qpic.cn/mmbiz_png/c8XbGB9esl9N3PYCKHkTYBicroEFZ4xEUyQClnloD1H0wKUdT2P8ZDTsKPNGbEibwcXZvm7mgY5tUmcWoib6vbSHA/640?wx_fmt=png)

虽然nginx是高性能的web服务器,但如果nginx一旦挂掉,会造成下游所有服务的不可用,既然要做到高可以用,那么所有单点导致整个系统不可用的情况都要进行考虑,如图所示,使用DNS轮询实现Nginx实现高可用。

通过Nginx负载均衡实现Tomcat的高可用,使用Redis集群实现缓存高可用,使用数据库主从读写分离来避免写入操作成为数据库瓶颈。

服务化架构

随着用户不断扩充,需求越来越复杂,系统耦合度越来越高,越来越不易维护,为了解决这种问题,我们需要对一个系统以业务为维度拆分出多个子系统,这样可以更清晰的体现出每个系统的职责,降低业务耦合,增加系统容错性,不会做到牵一发而动全身。

![](https://mmbiz.qpic.cn/mmbiz_png/c8XbGB9esl9N3PYCKHkTYBicroEFZ4xEUYPRL67mdibErpkiagibamOhuUCFcqYu3XzoE7cH76f2vbe6PjROshX8kQ/640?wx_fmt=png)

从上图看出,微服务是将一个系统以业务为维度拆分为若干个细小的子系统,每个子系统通常拥有独立的数据库和中间件,这样服务与服务之间耦合性降低了,也避免了一台服务出现故障而影响整个系统的情况。

高并发 (读)

某公司准备上线秒杀活动来增加公司品牌影响力,结果上线半小时候后出现DB被打挂,造成了非常不好的影响,Boos很生气,程序员阿飞赶快找问题,尽快让服务恢复正常。

![](https://mmbiz.qpic.cn/mmbiz_png/c8XbGB9esl9N3PYCKHkTYBicroEFZ4xEUH47xNwDtTcAgicKM9qIzDROS2CHwwvQjWk2VtDIFAg9EBiaSjaibrjHIg/640?wx_fmt=png)

**问题分析:**阿飞发现是缓存失效了,大量的请求打到了数据库,将所有服务重启后,服务恢复了正常,但发现总有部分缓存key实在是太过火爆,如果缓存失效,瞬间的流量就可以导致db满负载,导致服务不可用。

**解决难度:**★★★

解决思路

通过阿飞的问题分析,我们可以知道,基本上属于缓存问题。

缓存常见问题:缓存穿透、缓存雪崩、缓存击穿。

缓存穿透:通常指缓存的命中率问题,如果缓存的命中率过低,那么DB的负载就会增加,导致服务的不可用,通常情况下,我们会根据热点业务,去将热点数据加载到缓存中,但是如果存在一些非法请求,或者我们没有保护的的地方,就会出现缓存穿透的现象,大量请求打到了DB,引起服务崩溃。

缓存穿透解决,我们可以使用布隆过滤器以及缓存热点探测中间件。

缓存雪崩:阿飞公司系统在上线半小时后出现集中奔溃,可能存在缓存雪崩问题,通常是热点缓存集中过期,造成一段时间内DB没有收到保护,引起服务不可用

缓存雪崩解决 尽量分散缓存的过期时间。

缓存击穿:缓存击穿是指某个热点Key过期的瞬间,超高的请求同时进入到DB,直接打穿了缓存,全部落到了DB上,引起服务不可用。

缓存击穿如何解决,我们可以使用双重锁进行解决,保证每个时候,只有一个线程能进行访问DB,访问成功后,加入缓存,这样,其它的请求都只需要在缓存中读取即可。

线程锁只能锁住当前实例,分布式系统要锁住所有服务需要使用分布式锁来锁住所有服务。

分布式锁在以前栏目中也讲解到了详细使用方式,这里就不在赘述了。

其它更为严重的问题

如果是Redis不可用,如何解决。

使用少量本地缓存缓存非实时数据。

Redis 属于分布式缓存,如果是大量的读操作,并且不需非常实时(比如属于商品数量等信息)我们可以放入本地缓存,即使用户访问本地缓存获取的商品数量还存在,但是我们只要保证最终下单的时候,这个数据是从数据库获取的即可。

Redis实现高可用

主从模式(读写分离)来进行IO的扩充,一致性hash算法路由,这种模式简单粗暴,但是一旦主节点不可用,会导致Redis崩溃。

哨兵模式,Redis哨兵模式是给Redis系统添加了若干个节点对整个Redis进行监控,一旦发现故障,哨兵之间可以进行投票,选出新的主节点,进行主从切换。

Redis还拥有持久化策略,AOF和RDB

RDB是基于快照模式进行持久化,它会将Redis中的数据,定期或者根据配置进行持久化,RDB是Redis数据的非常紧凑的单文件时间点表示。RDB文件非常适合备份。

RDB对于灾难恢复非常有用,它是一个紧凑的文件,可以非常方便传输上。RDB最大限度地提高了Redis的性能与AOF相比,RDB允许大型数据集更快地重启。

AOF是基于写入操作来进行持久化的,每次写入都会进行持久化,所以丢失的数据通常在秒级(fsync是使用后台线程执行的,并且当没有fsync进行时,主线程将尝试执行写入操作。)但是您只能损失一秒钟的写入时间。AOF日志是仅追加的日志,因此,如果断电,则不会出现寻道或损坏问题。

AOF以易于理解和解析的格式包含所有操作的日志。您甚至可以轻松导出AOF文件。

对于同一数据集,AOF文件通常大于等效的RDB文件。根据确切的fsync策略,AOF可能比RDB慢

—— Redis.io

高并发 (写)

| 读不就是加缓存吗 写呢?|

场景一:某小公司分布式系统拥有10台机器,3台Redis实例,数据库2h4g主从,平时QPS在2W左右,某天该公司上线了优惠活动,由于活动真的很给力,QPS一下子从5w增加到了50w,尽管之前已经将热点数据存放到了Redis中,可是突然发现,主数据库CPU使用率依然高居不下,随着数据库越来越慢,各个服务的线程池池也均被打满,导致服务不可用。

**分析问题:**通过上述问题发现,发现最终问题是由存在的大量订单写入操作,写操作缓存抵挡不住,使数据库成为了瓶颈,数据库的缓慢使服务发生了级联效应,导致了服务的不可用。

**解决难度:**★★★★★

解决思路

优化公式:前端—>实例—>中间件—>DB

前端拦截:问题出现在大量写入操作,前端可以在下单时增加验证码,稍微降低同一时间用户访问量,并且可以拦截机器请求。

实例限流,发现瓶颈不在于服务,我们为了不影响用户体验,可以在这里不对实例进行限流。

消息削峰:在大量的写入情况,数据库可能扛不住,我们可以将业务进行改变,使用消息队列的方式,创建队列逐步消费,实现流量削峰填谷,下单成功后,异步的通知用户。

以下是公司真正的瓶颈所在,根据瓶颈级别可以分为几种不同程度的改进操作。

![](https://mmbiz.qpic.cn/mmbiz_png/c8XbGB9esl9N3PYCKHkTYBicroEFZ4xEUxtCmYS9ZFaRvOkcb9mx7remoibMXEgVBxSeZVdMOCZfkNjic0Jq2D1EQ/640?wx_fmt=png)

读写分离 水平分库架构

更换数据库 AliSql是阿里巴巴16年开源基于Mysql的改进数据库,其核心机制在与底层可以将热点数据进行Hash,当有高并发写入操作进行时,可进行暂存,当积累到一定数量直接进行批量写入操作,在高并发写入场景情况下比传统Mysql强出百倍。

读写分离:采取主从复制的情况,主负责写入,从负责读。

硬件提升:机械硬盘更换为固态硬盘,增加数据库单机CPU核心与内存。

DB扩容:当数据库成为最后的瓶颈,我们不得不依赖分库操作对数据库进行扩容,将一个数据库横向的复制多个,业务中使用中间件进行路由管理,多库解决单库瓶颈。

Meihua

亿万级流量 高并发 三板斧

| 缓存 限流 队列 |

动不动亿万级流量高并发,TB级数据增长,俗话说:量变引起质变,假设传统50台机器集群能够坚持1wQPS,如果是1亿人呢?需要50w台机器,这么多机器,如何管理?如何迭代?如何监控?

READING

![](https://mmbiz.qpic.cn/mmbiz_png/c8XbGB9esl9N3PYCKHkTYBicroEFZ4xEUXySnKjGCK1gPOgYeEWFpcJIlGCnrauLcuMF4xG1WZAibCv7mydAtahg/640?wx_fmt=png)

云原生时代 是开发者最好的时代

**分析问题:**到了亿级流量时代,就不是传统的加缓存,加机器那么简单了,难点在于超大规模集群如何管理,如何持续集成与持续部署?

**解决难度:**★★★★★★★★

解决思路

亿万级流量的系统从来不是一蹴而就,它是分阶段不断演化而来,例如阿里巴巴,03年是由PHP组成的web系统,演化到今天,拥有了自己的OceanBase,Spring Cloud Alibaba,消息中间件RocketMQ……

所幸正是因为架构不断演变的过程,出现了一些工具,持续集成持续部署Jenkins、虚拟化技术Docker,容器化编排与管理Kubernetes等。

![](https://mmbiz.qpic.cn/mmbiz_jpg/c8XbGB9esl9N3PYCKHkTYBicroEFZ4xEUTgkzKo7aWa4ib306mkd8SlbouDMd1kHiaqcNlagvO5t9TkdkicHCCLMQw/640?wx_fmt=jpeg)

因此,我们可以站在巨人的肩膀上,实现亿级流量的系统设计。

仔细想想,假设系统性能优化10ms,那么对于亿级别并发来说,就是10亿ms,并且根据蝴蝶效应这个效果会不断的放大,因此,高并发之路第一阶段:调优

分析调优之前,来整理一下我们整个服务调用链的思路。

用户请求→DNS→服务器

之前有讲过,DNS服务是支持轮询的,我们为了实现后台服务的高可用,可以在DNS层级设置轮询策略,路由到不同的web服务器。

虽然我们的应用服务器,可以部署前端静态资源文件,但服务之间最好遵循单一职责原则,因此我们要前后端分离,也叫动静分离,静态资源存放到偏向计算型的Nginx服务器中。(据说也可以放在OSS上,笔者没有尝试过)

以上操作完毕以后,我们还需要对静态资源添加CDN加速,CDN原理是全球多个CDN节点对你的静态资源进行缓存,用户会根据地区,访问最近的节点,因此页面更新时可能会存在一定时间的滞后性。CDN的好处是可以不用怕流量攻击,可以避免强大的流量并发,将服务器带宽打满。

后端调优实践,请求到了Nginx后,Nginx会反向代理到我们的内网集群环境,集群环境中就是一堆堆的实例了,说起调优,怎么不能提起JVM,JVM中,其实调优的地方也不是很多,主要是避免GC产生的StopTheWorld,我们可以针对一台实例的秒级内存数,来估算年轻代与老年代的堆内存合理分配(更多的堆内存增加吞吐率),并行垃圾的垃圾回收器ZGC、ZGC针对StopTheWorld进行优化,通过着色指针和读屏障技术,解决了转移过程中准确访问对象的问题,实现了并发转移。ZGC保证延迟时间不低于10ms的特性还是很值得期待的。

关于JVM调优等我 链路压测与调优专题哦

Jvm调优后,Sql调优就来了,SQL优化在剑指offer那一章上有详细的讲解,这里就不在赘述啦。

第二阶段 使用高并发三板斧

高并发三板斧之 缓存

缓存相比大家应该耳熟能详,前面高并发写章节也详细的讲解了Redis与本地缓存。

高并发三板斧之 消息中间件

消息中间件通俗的将就是消息队列,消息生产者(发布者)会将请求(消息)存放到一个高可用的分布式队列中,另一端消息消费者(订阅者)对队列中消息进行消费,使用消息队列的场景和优势:当并发很大的时候,我们的数据库可能承担不了那么多的写入操作,我们可以使用个容器,讲这些请求装起来,然后慢慢的进行处理,处理完毕后,再对用户进行通知,这样就能起到一个流量削峰的作用。

高并发三板斧之 限流

限流其实是丢车保帅的做法,舍弃部分请求保证我们服务的可持续运行,限流会影响到实际用户体验,所以我们最后才会考虑它,常见的限流算法有漏桶:可以这样理解,一个桶装满了水,水不断的流出,请求的线程可能根据水流出的速率决定能不能够进行请求。

令牌桶,通过在一个桶中放置令牌,拿到令牌的线程可以进行请求,没拿到的则拒绝,这种方式可以实现限流,并且拥有一定的弹性,例如桶中的令牌空闲时可以多一点,并不是匀速的。

上面通常是在Nginx或者在实例中实现限流,那么在我们的微服务架构中,通常会使用服务保护与降级组件,例如Sentinel和Hystrix实现服务限流降级熔断,来对服务进行一定的保护。

限流:我们通过压测计算服务的实际最大的负载数,设置限流数秒级最大负载数的80%(二八定理性能较优),这样就对流量进行整型,保证在高并发请求的情况下我们服务还是能进行任务的处理。

熔断:在微服务架构中,一个请求通常是一条复杂的服务调用链,假设某请求是A调B B调C、C调D,结果C调D发生了异常,或者是D服务不可用,那么下次继续出现该请求,我们应在A调B时就及时的将服务进行熔断,因为我们已经知道了D服务不可用,这请求已经是无意义操作,由此还能够避免因服务链路复杂,超时重试带来的级联效应。

降级:降级也是一种弃车保帅的做法,当大量的请求来临时,为了保证核心业务的运行,我们可以将非核心业务进行降级,例如阿里双十一当天为了减少服务器压力,不允许用户当天退款(退款是高并发写操作),这些业务层面优化的操作能从另一个维度大大减少我们服务器的压力。

Sentinel和Hystrix如何抉择?

Sentinel是SpringCloudAlibaba下的一款流量控制服务保护组件,底层基于信号量进行服务限流控制,并且在不断维护升级,并且在慢慢的支持其他语言,目前Sentinel社区也在不断的发展,也是使用者较多的一款组件。

Hystrix是SpringCloud早期就推出的服务保护组件,限流基于线程池,因此每个策略限流都要创建一个线程池通过控制线程数进行限流,不够灵活,性能上不如Sentinel,但是基于线程池限流的好处在于策略中实现了真正隔离。另外Hystrix停止维护,功能上不如Sentinel全面。

数据存储系统设计,当系统升级到亿万级并发时,往往要使用HDFS、etcd、Spark、ELK等来应对PB级数据的增长,不过大数据不是我们高并发的核心,我们在这里主要讨论瓶颈与突破。

数据库瓶颈突破

高并发下数据库瓶颈突破思路与实践

一、硬件提升 CPU IO 磁盘 内存 (能够直接用钱解决的问题永远比找人简单高效)

二、数据库调优 索引优化,优化慢查询(高性能Mysql中有详细讲解)

三、引入缓存,MongoDB、Redis、本地缓存,减少数据库IO

四、数据库表业务优化,重构

五、读写分离 主库负责写入,从库负责读,主从之间进行数据同步,减少读写压力。

六、分库分表 分库分表分为垂直分库分表和水平分库分表,垂直分库分表是跟业务进行独立拆分,库与表具有唯一性,而水平分库分表是则是进行横向复制副本,利用业务key进行策略路由,路由到指定的表或库中(最佳实践请看和后续专辑)。

水平分库分表或垂直分库分表能够突破数据库的瓶颈,但是为什么是最后一步,因为它的落地与实践通常具有非常大的挑战性,例如数据库多库后,如何保证数据一致性(分布式事务),Join操作,运维成本等。

一系列流程与瓶颈突破后,大家是不是觉得到这里就OK了呢,好戏才刚刚开始。

云原生

量变引起质变,简单的集群项目也许无需引入DevOps这种持续集成与部署思想,如果想要构建支持亿万级并发的万台集群规模,则你需要对下列知识进行探索与实践。

云原生探索

什么是云原生?CNCF 对云原生的定义:云原生技术有利于各组织在公有云、私有云和混合云等新型动态环境中,构建和运行可弹性扩展的应用。云原生的代表技术包括容器、服务网格、微服务、不可变基础设施和声明式 API。

容器:最为典型的就是Docker同学,docker是一种容器化技术,可以给程序提供统一的运行环境,并且它的镜像能非常方便我们应用程序的迁移、秒级启动,并且它损耗的性能很低,因此它是云时代发展最亮眼的星。

有了docker这位同学后,我们可以将我们的服务部署成docker容器,轻松的进行部署,当我们的系统很大时,我们就拥有了成千上万个容器,这些容器如何管理,如何发布。

微服务:微服务是一种分布式架构思想,以业务为维度拆分为职责单一的分布式系统。

服务网格:微服务与服务网格化如图所示:

![](https://mmbiz.qpic.cn/mmbiz_png/c8XbGB9esl9N3PYCKHkTYBicroEFZ4xEUqdV6zZhRDE8m7IUicdPiaiaD5icZZANZ9AT3ubzd13URrs9HG6LgnpYQ6w/640?wx_fmt=png)

服务网格

我们来看看传统集群的痛点,在我们只有一台单机应用时,我们所有的构建发布,只需一人进行管控足以,假设我们为了针对亿级流量并发,我们的服务器扩容到了50w台,假设每个人能发布与管理100台机器我们也需要5000人帮我们做这些运维操作,这无异于增加了高昂的人力成本,例如亚马逊一年持续集成与交付5000w次,平均每分钟发布95次,因此构建亿万级并发系统,必须得使用自动化运维工具,来帮助我们进行发布,持续集成与持续部署。

![](https://mmbiz.qpic.cn/mmbiz_png/c8XbGB9esl9N3PYCKHkTYBicroEFZ4xEUficUZno4BdCiayoEVcEvhd2KD0oicBlMeMXr0BZr1LsgvLPxV4iaAfoJHg/640?wx_fmt=png)

Kubernetes

我们今天的大佬就要登场~Kubernetes,俗称K8s,相信部分同学已经听说过他的大名,出身豪门(谷歌)、Kubernetes 项目自 2014 年 6 月开源以来,在众多厂商和开源爱好者的共同努力下迅速崛起,时至今日已成长为容器管理领域的事实标准。凭借超前的设计理念、开放的参与门槛、国内外大厂和开发者的大力支持,它的成功不言而喻。

Kubernetes 在云原生时代具有绝对的统治地位,那么它是干什么的呢?

K8s是一个管理容器编排的工具,使用它能够更方便的帮助我们管理容器,发布服务,实现对分布式系统的监控,CICD,伸缩。

![](https://mmbiz.qpic.cn/mmbiz_png/c8XbGB9esl9N3PYCKHkTYBicroEFZ4xEUicaT3fGxwWqy2sA8gMw3m4Yw4pxFUzHG9GOmicjtlrSZ1fQaM7QZgswg/640?wx_fmt=png)

云时代我们的服务通常运行在容器中,正如我之前所说,那么多的容器如何进行管理,这时我们拥有了一位管家K8s,他能够自动的对我们的容器进行管理,发布,服务监控,部署,伸缩。

K8s是由Mastar和Node多节点组成的集群环境,Mastar负责对所有子节点的调度,不负责容器运行,Node负责容器运行。

![](https://mmbiz.qpic.cn/mmbiz_png/c8XbGB9esl9N3PYCKHkTYBicroEFZ4xEUxhicOS3TSVT8pmxmnfXzBIPb2VOZ7s5Gakh9n6HD9JIsfzo9M14W0vw/640?wx_fmt=png)

笔者在学习 云原生容器实践

云原生落地与实践能解决什么问题?

服务弹性扩容:根据服务负载情况,智能判断该服务扩容。

服务异常智能发现迁移:对异常服务进行更换节点迁移。

智能流水线CICD,持续集成与持续部署。

落地云原生 服务秒级扩容,异常自动修复,流水线可视化CICD、这都是我们在大规模集群下需要探索的技能,目前云原生概念正在初露锋芒,保持关注~

遇见风华

不积小流,无以成江海~

这一期 感觉要耗尽毕生所学了哈哈

虽说有些纸上谈兵

但是提到的每个地方都是经过大规模考验的

数风流人物 还看今朝

亿万级并发系统 也许有一天由您掌控~

小人生 |大梦想 感谢关注

微信号|iHuazai-

原文链接|mp.weixin.qq.com/s/cQhzasZPr…

往期推荐 ●●