秒杀系统设计实战及思路

目标

  • 秒杀系统高并发的服务器架构设计和实践,保证服务不会因为流量超载而中断。

特点

  • 瞬时并发访问量比较高
  • 读多写少,读操作比较简单
  • 写 强一致性:例如:商品的库存跟卖出的商品数保持实时一致。
  • 读 弱一致性:例如:可以读到有库存,但是下单时没有库存,(12306就是看到有票,但是买的时候提示卖完了)。

难点

稳定性难

  • 高并发下,某个小依赖可能会雪崩
  • 流量预期难精确,过高也造成雪崩
  • 分布式集群,机器多,出故障概率高

准确性难

  • 库存、抢购成功数、创建订单数要保持一致

高性能难

  • 有限成本下需要做到极致性能

基本需求

![图片alt](/media/article/image/2021-01-04/1609750510226.png ‘‘图片title’’)

使用预扣库存方案

  • 创建订单先扣库存,支付超时时,把库存还回。

设计思路

减而治之

  • CDN原理:减少读的压力
  • nginx限流:进行过载保护
  • 异步队列:均匀分配流量

分而治之

  • nginx负载均衡:分散流量

实现

  • 我们可以把秒杀活动分为三个部分

活动开始之前

问题

  • 这个阶段用户会大量刷新详情页,导致详情页瞬时访问请求激增。

解决

  • 使用CDN服务或者浏览器缓存服务,减少服务器的压力,保证详情页能够正常访问

活动开始(查库存)

问题

  • 用户大量点击秒杀按钮,导致大量并发请求查询库存。

解决办法

服务器接入层

  • 限制前端按钮点击频率,防止用户瞬时重复点击,减少服务器接入层的压力,如:1s内只能按一次或只有一次有效。

服务器server层

  • 服务器接入层限制单个IP的访问访问频率,减少服务器server层的压力,如:1个IP在1s内只有一个请求有效,其他请求直接返回503显示错误提示页。
  • 服务器接入层限制最大并发连接数和总连接数,防止流量过载导致服务器崩溃。

数据库层

  • 使用Redis缓存,减少对数据库层的读压力。

活动进行(扣库存)

问题一

  • 当查到库存有余量时,就会进行扣除库存和处理订单,此时面临,查库存和扣库存的一致性问题。

解决办法一

  • 由于查库存和扣库存是两个操作,无法保证其一致性,可以使用Lua脚本原子性的去执行这两个操作。(Redis会把Lua脚本当成一个操作,执行过程中不会被打断)

解决办法二

  • 使用Redis分布式锁,先让客户端去申请分布式锁,只有拿到分布式锁的客户端才会进行查库存和扣库存的操作。

问题二

  • 查库存和扣库存都是在Redis中进行的,操作简单再加上Redis的高性能,是可以抗住压力,但是库存扣除成功后,数据库面临大量的订单的写入问题。

解决办法

  • 创建Redis消息队列,把写入订单的请求存到Redis中,数据库读取Redis消息队列中的写入订单的请求,进行批量写入。(也可以使用专门的消息队列服务Kafka、rabbitmq)

架构设计

架构原则

稳定性

  • 减少第三方依赖,同时自身服务部署也需要做到隔离
  • 压测、降级、限流方案、确保核心服务可用
  • 需要健康度检查,整个链路避免单点

高性能

  • 缩短单请求访问路径,减少IO
  • 减少接口数、降低吞吐数据量(精简字段名字、字段长度),请求次数减少

CDN架构

![图片alt](/media/article/image/2021-01-05/1609827098974.png ‘‘图片title’’)

服务架构

![图片alt](/media/article/image/2021-01-05/1609827318616.png ‘‘图片title’’)

架构优化

![图片alt](/media/article/image/2021-01-05/1609827560868.png ‘‘图片title’’)

提高单台服务器性能

减少IO

  • 使用本地内存

减少CPU上下文切换(多进程切换,多线程切换)

  • 单进程多协程
  • 异步IO

压测工具

安装

yum -y install httpd-tools ab

使用

  • -n表示总计请求多少次,-c表示一次并发是多少
ab -n 100 -c 10 [url]

nginx限流

配置

  • 连接数限制,即并发数(ngx_http_limit_conn_module)
  • 请求速率限速,限制ip在单位时间内的请求数(ngx_http_limit_req_module)

nginx.conf配置

limit_req_zone  $binary_remote_addr zone=mylimit:10m rate=1r/s;

server{
	# 可以配置到整个php,也可以配置到指定访问地址
	location ~ \.php$ {
		limit_req zone=mylimit burst=1 nodelay;
	}
	# 最大并发连接数100
	limit_conn  one  100;
	# 该服务提供的总连接数不得超过1000,超过请求的会被拒绝
	limit_conn perserver 1000;
}

第一段配置参数

  • $binary_remote_addr :表示通过remote_addr这个标识来做限制,“binary_”的目的是缩写内存占用量,是限制同一客户端ip地址
  • remote_addr变量的长度为7字节到15字节。
  • binary_remote_addr变量的长度是固定的4字节。
  • zone=mylimit:10m:表示生成一个大小为10M,名字为one的内存区域,用来存储访问的频次信息
  • rate=1r/s:表示允许相同标识的客户端的访问频次,这里限制的是每秒1次,即每秒只处理一个请求,还可以有比如30r/m的,即限制每2秒访问一次,即每2秒才处理一个请求。

第二段配置参数:

  • zone=mylimit :设置使用哪个配置区域来做限制,与上面limit_req_zone 里的name对应
  • burst=1:重点说明一下这个配置,burst爆发的意思,这个配置的意思是设置一个大小为1的缓冲区当有大量请求(爆发)过来时,超过了访问频次限制的请求可以先放到这个缓冲区内等待,但是这个等待区里的位置只有1个,超过的请求会直接报503的错误然后返回。
  • nodelay:如果设置,会在瞬时提供处理(burst + rate)个请求的能力,请求超过(burst + rate)的时候就会直接返回503,永远不存在请求需要等待的情况。(这里的rate的单位是:r/s)如果没有设置,则所有请求会依次等待排队。当前设置是1r/s,burst=1,表示1s内有2个请求是可以正常返回的。

限流算法

  • 令牌桶:均匀的生成令牌放到令牌桶中,每请求一次就从令牌桶中扣除一个令牌,当令牌桶中没有令牌时,返回503(可以处理突发流量)
  • 漏桶:把请求放到漏桶中进行排队,通过漏桶均匀流出,桶满则溢出返回503。(不能处理突发流量)
  • 计数器:(在应用层)单位时间内计数器进行计数,超过计数则返回503。

CDN

  • 内容分发网络(content delivery network)
  • 缩短访问路径,减少源站压力,提高内容响应速度
  • 为源站提供安全保护

![图片alt](/media/article/image/2021-01-04/1609743273728.png ‘‘图片title’’)

![图片alt](/media/article/image/2021-01-04/1609743320532.png ‘‘图片title’’)

![图片alt](/media/article/image/2021-01-04/1609743391228.png ‘‘图片title’’)

server层带权轮询

upstream test{
	service 192.168.0.2:8080 weight=1;  # 表示占所有服务器的1/4
	service 192.168.0.2:8080 weight=3;  # 表示占所有服务器的3/4
}

消息队列

  • 实际是链表,头插尾出,高并发下容易发生堵塞,为避免消息丢失,可通过写入实时消息队列进行延时处理。

消息队列 作用

  • 提高请求响速度,通过异步,如:创建订单发push,短信提醒等。
  • 高并发时,可以起到削峰,如:双十一创建订单
  • 延时队列,时间维度任务触发,如:发货提醒
评论