1 集群部署(负载均衡)
通过负载均衡减轻单机压力
如果一个应用只部署一台服务器,那抗住的流量请求是非常有限的。并且,单体的应用,有单点的风险,如果它挂了,那服务就不可用了。
因此,设计一个高并发系统,我们可以分而治之,横向扩展。也就是说,采用分布式部署的方式,部署多台服务器,把流量分流开,让每个服务器都承担一部分的并发和流量,提升整体系统的并发能力
高并发首选方案就是集群化部署,一台服务器承载的QPS有限,多台服务器叠加效果就会有明显提升。
集群化部署,就需要考虑如何将流量转发到服务器集群,这里就需要用到负载均衡,如LVS(Linux Virtual Server)和nginx。
常用的负载均衡算法有轮询法、随机法、源地址哈希法、加权轮询法、加权随机法、最小连接法等。
2 多级缓存
包括静态数据使用CDN、本地缓存、分布式缓存等,以及对缓存场景中的热点key、缓存穿透、缓存并发、数据一致性等问题的处理。
CDN:加速静态资源访问;将静态资源分发到位于多个地理位置机房的服务器,可以做到数据就近访问,加速了静态资源的访问速度,因此让系统更好处理正常别的动态请求
本地缓存:
分布式缓存:Redis单机就能轻轻松松应对几万的并发,读场景的业务,可以用缓存来抗高并发
3 分库分表、主从分离
-
分库分表:当业务量暴增的话,MySQL单机磁盘容量会撑爆。并且,我们知道数据库连接数是有限的。在高并发的场景下,大量请求访问数据库,MySQL单机是扛不住的!高并发场景下,会出现too many connections报错。
所以高并发的系统,需要考虑拆分为多个数据库,来抗住高并发的毒打。而假如你的单表数据量非常大,存储和查询的性能就会遇到瓶颈了,如果你做了很多优化之后还是无法提升效率的时候,就需要考虑做分表了。一般千万级别数据量,就需要分表,每个表的数据量少一点,提升SQL查询性能。
-
主从分离:通常来说,一台单机的MySQL服务器,可以支持500左右的TPS和10000左右的QPS,即单机支撑的请求访问是有限的。因此你做了分布式部署,部署了多台机器,部署了主数据库、从数据库。
但是,如果双十一搞活动,流量肯定会猛增的。如果所有的查询请求,都走主库的话,主库肯定扛不住,因为查询请求量是非常非常大的。因此一般都要求做主从分离,然后实时性要求不高的读请求,都去读从库,写的请求或者实时性要求高的请求,才走主库。这样就很好保护了主库,也提高了系统的吞吐。
当然,如果回答了主从分离,面试官可能扩展开问你主从复制原理,问你主从延迟问题等等,这块大家需要全方位复习好哈。
4 NoSQL
考虑NoSQL数据库的使用,比如HBase、TiDB、ClickHouse等,但是团队必须熟悉这些组件,且有较强的运维能力
5 异步化
对于处理耗时长的任务,如果采用同步等待的方式,会严重降低系统的吞吐量,可以采用异步化进行解决。
将次要流程通过多线程、MQ、甚至延时任务进行异步处理。
设计一个高并发的系统,需要在恰当的场景使用异步。如何使用异步呢?后端可以借用消息队列实现。比如在海量秒杀请求过来时,先放到消息队列中,快速相应用户,告诉用户请求正在处理中,这样就可以释放资源来处理更多的请求。秒杀请求处理完后,通知用户秒杀抢购成功或者失败
1)调用异步化
①:Callback:异步回调通过注册一个回调函数,然后发起异步任务,当任务执行完毕时会回调用户注册的回调函数,从而减少调用端等待时间。这种方式会造成代码分散难以维护,定位问题也相对困难
②:Future:当用户提交一个任务时会立刻先返回一个 Future,然后任务异步执行,后续可以通过 Future 获取执行结果
③:CompletableFuture :对多个异步编程进行编排,组成更复杂的异步处理,并以同步的代码调用形式实现异步效果
2)流程异步化
一个业务流程往往伴随着调用链路长、后置依赖多等特点,这会同时降低系统的可用性和并发处理能力;
可以采用对非关键依赖进行异步化解决,如MQ
6 MQ
对流量进行削峰填谷,通过MQ承接流量。
我们搞一些双十一、双十二等运营活动时,需要避免流量暴涨,打垮应用系统的风险。因此一般会引入消息队列,来应对高并发的场景。
假设你的应用系统每秒最多可以处理2k个请求,每秒却有5k的请求过来,可以引入消息队列,应用系统每秒从消息队列拉2k请求处理得了。
有些伙伴担心这样可能会出现消息积压的问题:
首先,搞一些运营活动,不会每时每刻都那么多请求过来你的系统(除非有人恶意攻击),高峰期过去后,积压的请求可以慢慢处理;
其次,如果消息队列长度超过最大数量,可以直接抛弃用户请求或跳转到错误页面;
7 并发处理
1)请求并发
如果一个任务需要处理多个子任务,可以将没有依赖关系的子任务并发化,这种场景在后台开发很常见。如一个请求需要查询 3 个数据,分别耗时 T1、T2、T3,如果串行调用总耗时 T=T1+T2+T3。对三个任务执行并发,总耗时 T=max(T1,T 2,T3)。同理,写操作也如此。对于同种请求,还可以同时进行批量合并,减少 RPC 调用次数
2)冗余请求
冗余请求指的是同时向后端服务发送多个同样的请求,谁响应快就是使用谁,其他的则丢弃。这种策略缩短了客户端的等待时间,但也使整个系统调用量猛增,一般适用于初始化或者请求少的场景
8 缓存预热
通过异步任务提前预热数据到本地缓存或者分布式缓存中。
9 减少IO次数
比如数据库和缓存的批量读写、RPC的批量接口支持、或者通过冗余数据的方式干掉RPC调用。
如将多次单个的请求,优化为一次批量请求,减少网络IO;
对应MySQL就是批量插入,批量查询;
因为每次建立连接,数据交互,释放连接都会消耗大量的资源,同时涉及到用户态到核心态的切换
10 减少IO数据包大小
采用轻量级的通信协议、合适的数据结构、去掉接口中的多余字段、减少缓存key的大小、压缩缓存value等。
11 接口优化
设计一个高并发的系统,需要设计接口的性能足够好,这样系统在相同时间,就可以处理更多的请求。
12 jvm优化
包括新生代和老年代的大小、GC算法的选择等,尽可能减少GC频率和耗时。
13 池化技术
各种池化技术的使用和池大小的设置,包括HTTP请求池、线程池(考虑CPU密集型还是IO密集型设置核心参数)、数据库和Redis连接池等。
在高并发的场景下,数据库连接数可能成为瓶颈,因为连接数是有限的。
我们的请求调用数据库时,都会先获取数据库的连接,然后依靠这个连接来查询数据,搞完收工,最后关闭连接,释放资源。如果我们不用数据库连接池的话,每次执行SQL,都要创建连接和销毁连接,这就会导致每个查询请求都变得更慢了,相应的,系统处理用户请求的能力就降低了。
因此,需要使用池化技术,即数据库连接池、HTTP 连接池、Redis 连接池等等。使用数据库连接池,可以避免每次查询都新建连接,减少不必要的资源开销,通过复用连接池,提高系统处理高并发请求的能力。
同理,我们使用线程池,也能让任务并行处理,更高效地完成任务。
常见的池化技术有内存池、线程池、连接池、对象池等