高并发是目前很多人都会遇到的问题,而且具有一定的难度,今天我将从网络 => 中间件 => 数据库,一共三个层面提出一些解决方案来应对高并发的请求。
网络方面
对于高并发,首先就是可以在网络请求最外层进行限流和负载均衡,那么最先想到的就是nginx
nginx负载均衡
nginx的负载均衡可以很大程度上解决高并发的请求流量,但是也只能对应是分布式服务,如果是单机服务就很有限了。
nginx的负载均衡有很多种算法,包括轮询,加权轮询,最少使用,IP哈希,这些算法nginx都已经实现了,只要我们在配置文件里面配置一下就OK啦。
请求限流
请求限流主要是在一段时间内限制请求的个数,防止多个请求造成服务器崩溃。这是并发请求的解决方案
主要的算法就是滑动窗口和令牌桶的算法。关于这两个算法的实现方式大家可以参考网上比较多的文章和第三方库
http缓存
http缓存是客户端缓存,浏览器作为客户端接受到服务端响应后,对于响应首部字段进行解析,分析出相应的缓存规则,将资源按规则进行缓存,再次请求时如果命中缓存则直接读取本地缓存不再发出请求。
http缓存规则由响应首部字段进行控制,其中的关键字段有Expires,Cache-Control ,Last-Modified ,Etag 四个字段,Expires和Cache-Control用来确定确定缓存的存储时间,Last-Modified 和Etag则用来确定缓存是否要被更新,我们简单来看一下区别。
-
expires: HTTP1.0中用来控制缓存时间的参数,响应头包含日期/时间, 即在此时间之后,响应过期。
-
cache-control: HTTP1.1中用来控制缓存时间的参数
- public: 表明响应可以被任何对象(包括:发送请求的客户端,代理服务器,等等)缓存。
- private: 表明响应只能被单个用户缓存,不能作为共享缓存(即代理服务器不能缓存它)。
- max-age=: 设置缓存存储的最大周期,相对于请求的时间缓存seconds秒,在此时间内,访问资源直接读取本地缓存,不向服务器发出请求。(与expires同时出现时,max-age优先级更高)
- s-maxage=: 规则等同max-age,覆盖max-age 或者 Expires 头,但是仅适用于共享缓存(比如各个代理),并且私有缓存中它被忽略。(与expires或max-age同时出现时,s-maxage优先级更高)
- no-store: 不缓存服务器响应的任何内容,每次访问资源都需要服务器完整响应
- no-cache: 缓存资源,但立即过期,每次请求都需要跟服务器对比验证资源是否被修改。(等同于max-age=0)
-
Last-modified: 源头服务器认定的资源做出修改的日期及时间。精确度比Etag低。包含有If-Modified-Since或 If-Unmodified-Since首部的条件请求会使用这个字段。
-
Etag: HTTP响应头是资源的特定版本的标识符。
CDN缓存
主要作用是减少服务器的压力,提升响应速度。
将静态资源(图片、CSS、JS等)缓存到CDN节点,用户请求时直接从CDN获取。
CDN缓存对于每家CDN商家的策略稍微有些不同,但是大致可以参考http缓存的设置
具体的实现有Nginx的静态资源分发策略、阿里云OSS、AWS S3等云存储结合CDN。
中间件
反向代理和网关
这一步主要是做对请求的预处理,认证,限流等。对于限流的方法,主要是计数法,滑动窗口法,漏斗法和令牌桶
这几种方法建议大家查看详细介绍的文章,弄懂原理和简单实现尤其是令牌桶和滑动窗口。面试中也是比较容易考察的
中间件
使用RabbitMQ、Kafka、ActiveMQ, 将耗时且不紧急的任务放入队列中,后续可以异步处理。
对于go语言来说,也可以使用channel来实现中间件效果。
定义一个有缓存的channel,每个请求来都像channel发送一个信号(可以是空结构体),利用channel的阻塞来判断是否有能力处理新的请求
分布式缓存
作用:提高数据查询效率,减少对数据库的直接访问压力。
实现方式:使用Redis、Memcached等分布式缓存,存储用户会话信息、热门数据、计算结果等。
缓存策略:设置合理的过期时间和淘汰策略(LRU、LFU等)来管理缓存数据
数据库层面的优化方法
数据库是系统的核心,承载着存储和查询的主要任务。在高并发环境下,数据库容易成为瓶颈,因此需要采取各种策略来优化数据库性能。
(1) 读写分离
-
作用:将读操作和写操作分离,减轻主数据库的负担。
-
实现方式:
- 主从复制:主库负责写操作,从库负责读操作,通过同步机制保持数据一致性。
- 应用层分发:业务逻辑根据请求的操作类型,自动将读请求路由到从库。
(2) 分库分表
-
作用:将数据拆分到多个数据库或表中,减少单个库或表的负载。
-
实现方式:
- 水平分库分表:根据某个字段(如用户ID、订单ID)进行分库分表。
- 垂直分库分表:按照业务模块将不同的业务数据拆分到不同的数据库中。
(3) 索引优化
-
作用:提高查询效率,减少扫描全表的开销。
-
实现方式:
- 合理设计索引:根据查询条件,优化索引结构,避免冗余或无效索引。
- 覆盖索引:让索引包含查询所需的所有字段,避免回表查询。
(4) 缓存与数据库结合
-
作用:通过缓存减少对数据库的直接访问,提升整体查询性能。
-
实现方式:
- Redis缓存:对于频繁查询的数据,将其缓存到Redis中,减轻数据库压力。