转载

如何设计一个支持高并发的高可用系统?

如何设计一个支持高并发的高可用服务?在前期设计时应该从哪些方面入手?
明确的一点:没有哪一个系统是从一开始设计时就是高可用的,支持高并发的。都是在产品的发展壮大中,随着业务量的增加,逐渐对系统架构进行一步步升级。

一. 面试官心理分析

说实话,如果面试官问你这个题目,那么你必须要使出全身吃奶劲了。为啥?因为你没看到现在很多公司招聘的 JD 里都是说啥,有高并发就经验者优先。

如果你确实有真才实学,在互联网公司里干过高并发系统,那你确实拿 offer 基本如探囊取物,没啥问题。面试官也绝对不会这样来问你,否则他就是蠢。

假设你在某知名电商公司干过高并发系统,用户上亿,一天流量几十亿,高峰期并发量上万,甚至是十万。那么人家一定会仔细盘问你的系统架构,你们系统啥架构?怎么部署的?部署了多少台机器?缓存咋用的?MQ 咋用的?数据库咋用的?就是深挖你到底是如何扛住高并发的。

因为真正干过高并发的人一定知道,脱离了业务的系统架构都是在纸上谈兵,真正在复杂业务场景而且还高并发的时候,那系统架构一定不是那么简单的,用个 redis,用 mq 就能搞定?当然不是,真实的系统架构搭配上业务之后,会比这种简单的所谓“高并发架构”要复杂很多倍。

如果有面试官问你个问题说,如何设计一个高并发系统?那么不好意思,一定是因为你实际上没干过高并发系统。面试官看你简历就没啥出彩的,感觉就不咋地,所以就会问问你,如何设计一个高并发系统?其实说白了本质就是看看你有没有自己研究过,有没有一定的知识积累。

最好的当然是招聘个真正干过高并发的哥儿们咯,但是这种哥儿们人数稀缺,不好招。所以可能次一点的就是招一个自己研究过的哥儿们,总比招一个啥也不会的哥儿们好吧!

所以这个时候你必须得做一把个人秀了,秀出你所有关于高并发的知识!

二. 面试题剖析

其实所谓的高并发,如果你要理解这个问题呢,其实就得从高并发的根源出发,为啥会有高并发?为啥高并发就很牛逼?

我说的浅显一点,很简单,就是因为刚开始系统都是连接数据库的,但是要知道数据库支撑到每秒并发两三千的时候,基本就快完了。所以才有说,很多公司,刚开始干的时候,技术比较 low,结果业务发展太快,有的时候系统扛不住压力就挂了。

当然会挂了,凭什么不挂?你数据库如果瞬间承载每秒 5000/8000,甚至上万的并发,一定会宕机,因为比如 mysql 就压根儿扛不住这么高的并发量。

所以为啥高并发牛逼?就是因为现在用互联网的人越来越多,很多 app、网站、系统承载的都是高并发请求,可能高峰期每秒并发量几千,很正常的。如果是什么双十一之类的,每秒并发几万几十万都有可能。

那么如此之高的并发量,加上原本就如此之复杂的业务,咋玩儿?真正厉害的,一定是在复杂业务系统里玩儿过高并发架构的人,但是你没有,那么我给你说一下你该怎么回答这个问题:

可以分为以下 8 点:

  • 系统拆分
  • Cache(缓存)
  • MQ
  • 数据库拆分(分库分表)
  • 读写分离
  • ElasticSearch
  • HTML 页面静态化
  • CDN 加速

2.1 系统拆分

将一个系统拆分为多个子系统,使用 Spring Cloud 来做。然后每个系统连一个数据库,这样本来就一个库,现在多个数据库,不也可以扛高并发么。

2.1.1 系统拆分成多个应用有以下优点:

1. 系统计算订单能力提升,提高扩展性。如果应用HTTP连接数不够,但是CPU和内存占用不高,这时候就可以只扩展Web_Tomcat,因为一个应用只接受用户创建订单,不做其他复杂计算逻辑,可以部署在一个低配置的Docker上。
2. 应用与应用之间从物理上进行隔离。随着业务的发展,业务逻辑代码也随着改变。虽说在设计时会考虑到以后的可扩展性,但还是会有代码上的更新维护(系统上线后程序员就可以下岗了)。订单的生产逻辑大都在Order_Produce和Manager_Tomcat应用上,Web_Tomcat仅仅接受用户下单,逻辑简单一般不会发生改变。这时候Order_Produce的版本迭代不会对Web_Tomcat造成风险,就算发布失败,也不会影响用户下单。把可预见到的改变和不变进行隔离,也就是关注点分离。
3. 为以后的团队拆分打好基础。支付应用Pay_Tomcat会涉及到跟银行对接,财务对账等一系列操作,而且这部分操作跟某个具体的业务没有关系。随着公司团队的发展壮大,这个应用就可以建设一个独立的团队进行维护。
4. 提升对变化的响应速度。因为子系统的功能独立,由独立的团队进行维护,独立的技术栈。对新需求的响应,不需要依赖其他应用,需要升级时可以选择合适的技术栈。代码量小,重构起来也方便。

2.1.2 任何事物都是双面的,优点与缺点是共存的。缺点是:

1. 拆分之后系统的复杂度提高,原来运行在一个JVM进程中的应用会运行在多个JVM中。
2. RPC框架的选择
多个JVM进程间如何通信,该如何选择RPC框架,是选择同步处理还是异步处理,这都是需要架构师考虑的。一般需要立即获取执行结果的调用选择同步,这类型的框架有dubbo thirft webservice hession等,也可选择HTTP RESTfull。如果不需要立即得到执行结果,只是通知远端的JVM或者只需要发送一条数据,至于远端的JVM什么时候执行无需关注,则可以选择使用MQ,这类型框架有Active MQ  、Rabbit MQ等。
3. 依赖复杂度提高
在一个单体应用上执行可能不需要调用远端服务或者很少涉及到对第三方服务的调用,但是如果把一个单块应用拆分成多个,就会增加依赖,拆分的粒度越细依赖复杂度越高,这也是微服务设计时的一个难点。
依赖复杂度高了,相应的对每个依赖的管理越严格了。比如给每个依赖分配的线程个数CPU时间片网络IO等,不能因为某一个外部依赖响应延迟就导致其他服务不可用,这是不可容忍的。关于如何分配资源,监控依赖和实现FastFail可以参考
4. 分布式事务
不论是系统拆分成多个子系统还是一个单块应用拆分成多个微服务,很多时候都会从功能上考虑拆分,指责单一高内聚原则,尽量避免出现分布式事务问题。如果无法避免可以从以下两方面考虑:强一致性和最终一致性。强一致性需要让每一个参与事务的服务都能提供undo操作,类似于2PC(二阶段提交)3PC。弱一致性就可以通过异步实现最终一致性。

2.2 缓存

这也是常见的一种优化方式,在数据库层之上加一层缓存,减少对数据库的访问压力。缓存中的数据都是存储在内存里的,而数据库中的数据是写在磁盘上的,访问内存肯定是比访问磁盘快的可不止一个数量级。

大部分的高并发场景,都是读多写少,那你完全可以在数据库和缓存里都写一份,然后读的时候大量走缓存不就得了。毕竟人家 redis 轻轻松松单机几万的并发。所以你可以考虑考虑你的项目里,那些承载主要请求的读场景,怎么用缓存来抗高并发

2.3 MQ

MQ,必须得用 MQ。可能你还是会出现高并发写的场景,比如说一个业务操作里要频繁搞数据库几十次,增删改增删改,疯了。那高并发绝对搞挂你的系统,你要是用 redis 来承载写那肯定不行,人家是缓存,数据随时就被 LRU 了,数据格式还无比简单,没有事务支持。所以该用 mysql 还得用 mysql 啊。那你咋办?用 MQ 吧,大量的写请求灌入 MQ 里,排队慢慢玩儿,后边系统消费后慢慢写,控制在 mysql 承载范围之内。所以你得考虑考虑你的项目里,那些承载复杂写业务逻辑的场景里,如何用 MQ 来异步写,提升并发性。MQ 单机抗几万并发也是 ok 的,这个之前还特意说过。

2.4 (数据库拆分)分库分表

当数据量达到某个阀值时,数据库拆分就会成为一个紧急的需求。一般从业务上进行垂直拆分,如果业务单一,也可从水平上进行拆分。拆分的原则一般是:避免跨数据库事务和如何选择 shardingId。跨数据库事务可以选择在前期调研时把同一事务中的表放在一个数据库中。如果数据冷热不均 shardingId 可以是 UserPin 或者订单号 Hash 打散后的值,如果数据冷热均匀可以按段分库也可以对某一个值取模后的值。

将一个数据库拆分为多个库,多个库来扛更高的并发;然后将一个表拆分为多个表,每个表的数据量保持少一点,提高 sql 跑的性能。
这里推荐使用 Sharding-JDBC

2.5 读写分离

读写分离,这个就是说大部分时候数据库可能也是读多写少,没必要所有请求都集中在一个库上吧,可以搞个主从架构,主库写入,从库读取,搞一个读写分离。读流量太多的时候,还可以加更多的从库

2.6 ElasticSearch

Elasticsearch,简称 es。es 是分布式的,可以随便扩容,分布式天然就可以支撑高并发,因为动不动就可以扩容加机器来扛更高的并发。那么一些比较简单的查询、统计类的操作,可以考虑用 es 来承载,还有一些全文搜索类的操作,也可以考虑用 es 来承载。

2.7 CDN 加速

任何一个互联网系统面向的用户都遍布于地球的每个角落,每个角落的请求到机房可用多种路径可选,正所谓条条大路通罗马,这里是条条路径通机房。其中有速度快的路径有慢的路径,如何选择最优路径,把每个角落的请求快速的传递到机房,这就是 CDN 的功能。

2.8 HTML 页面静态化

这是最长见的一种优化方式,成本也最低,不需要考虑硬件成本。静态页面部署在 NGNIX 中,收到用户请求,Ngnix 不需要访问 Webapp 即可响应用户,减少应用渲染页面的时间,同时也降低了应用的压力。

三. 总结

上面的 8 点,基本就是高并发系统肯定要干的一些事儿,大家可以仔细结合之前讲过的知识考虑一下,到时候你可以系统的把这块阐述一下,然后每个部分要注意哪些问题,之前都讲过了,你都可以阐述阐述,表明你对这块是有点积累的。

说句实话,毕竟你真正厉害的一点,不是在于弄明白一些技术,或者大概知道一个高并发系统应该长什么样?其实实际上在真正的复杂的业务系统里,做高并发要远远比上面提到的点要复杂几十倍到上百倍。你需要考虑:哪些需要分库分表,哪些不需要分库分表,单库单表跟分库分表如何 join,哪些数据要放到缓存里去,放哪些数据才可以扛住高并发的请求,你需要完成对一个复杂业务系统的分析之后,然后逐步逐步的加入高并发的系统架构的改造,这个过程是无比复杂的,一旦做过一次,并且做好了,你在这个市场上就会非常的吃香。

其实大部分公司,真正看重的,不是说你掌握高并发相关的一些基本的架构知识,架构中的一些技术,RocketMQ、Kafka、Redis、Elasticsearch,高并发这一块,你了解了,也只能是次一等的人才。对一个有几十万行代码的复杂的分布式系统,一步一步架构、设计以及实践过高并发架构的人,这个经验是难能可贵的。

参考链接:
https://github.com/doocs/advanced-java/blob/master/docs/high-concurrency/high-concurrency-design.md
https://mp.weixin.qq.com/s/q8ILenmqIrfNhBB0wiETGw

本文目录