高并发操作
1. 背景
1.1. 发领券
接口接收商户号,卡券模板号和用户手机号,将预先生成的等发卡券关联到用户并记录发放状态。涉及流程操作如下:
- 查询卡券模板
- 查询此用户已经领取此卡券数量,如果用户已领卡数量达到领取限制,立即返回出错信息
- 查询可发放的卡券
- 修改此种卡券的库存
- 更改发放卡券的状态
- 修改用户和此卡券的关联
1.2. 性能目标
- 使用阿里4核16G公有支服务器
- 模拟100并用户并发
- CPU控制在20%以下
- 在200ms内完成所有请求
2. 数据表准备
2.1. 创建一个表用来查询目标对象的标签
IF OBJECT_ID('coupon') IS NOT NULL
DROP TABLE coupon;
GO
CREATE TABLE coupon
(
id INT IDENTITY
PRIMARY KEY ,
name NVARCHAR(255) NOT NULL ,
status INT NOT NULL ,
tag VARCHAR(36) NOT NULL ,
update_time DATETIME NOT NULL ,
create_time DATETIME NOT NULL ,
remark NVARCHAR(255) NOT NULL
);
GO
DECLARE @totalcount AS INT =10000000, @count AS INT = 0;
DECLARE @sql AS NVARCHAR(MAX);
WHILE @count < @totalcount
BEGIN
SELECT @sql = N'
INSERT INTO coupon
(
name ,
status ,
tag ,
update_time ,
create_time ,
remark
)
SELECT TOP ' + CONVERT(NVARCHAR(10), @totalcount - @count) + '
name = NEWID() ,
status = 0 ,
tag = '''' ,
update_time = GETDATE() ,
create_time = GETDATE() ,
remark = ''''
FROM
sys.all_objects a,
sys.all_objects b,
sys.all_objects c;';
EXEC( @sql);
SELECT @count = COUNT(*)
FROM coupon;
END;
SELECT COUNT(*)
FROM coupon WITH ( NOLOCK );
GO
2.2. 单点测试
SET NOCOUNT ON;
DECLARE @count AS INT = 0;
DECLARE @time AS DATETIME = GETDATE();
DECLARE @name AS NVARCHAR(100);
WHILE @count < 100
BEGIN
DECLARE @tag_txt VARCHAR(36) = CONVERT(VARCHAR(36), NEWID());
UPDATE TOP ( 1 )
coupon
SET tag = @tag_txt
WHERE tag = '';
SELECT TOP 1 @name = name
FROM coupon WITH (NOLOCK)
WHERE tag = @tag_txt;
SET @count = @count + 1;
END;
PRINT DATEDIFF(MILLISECOND, @time, GETDATE());
3. 优化
3.1. 创建索引
CREATE NONCLUSTERED INDEX IX_TAG ON coupon
(
tag ASC
);
在没有索引的情况下,100条记录平均要10s左右才能完成。而如果正确使用索引,性能很大提供到50ms左右。
然后我们使用asp.net core 开发一个api 接口,通过简单的测试,结果满足性能目标。后来我们把并发提高到1000依然可以在1s内完成操作。
当我们把数据提高到3000W,性能出现了明显的下滑。这很容易理解,一旦单表的数据超过1000W查询将变得异常的慢,这个时候我们只能通过分表或分库处理。
3.2. 分表分库
根据业务规则,我们可以把卡券分成:
- 预发行卡券
- 已发行卡券
- 已领取卡券(用户的卡券)
- 已核销卡券
- 已过期卡券 现在我们只需要根据上面的业务把不同状态的卡券定期同步到不同的表或库中就能实现分表分库。 ```C#
```