高并发下的秒杀设计

Api接口频率调用限制+Redis缓存+消息队列


Api接口频率调用限制

参考资料:https://github.com/stefanprodan/AspNetCoreRateLimit/wiki/IpRateLimitMiddleware#defining-rate-limit-rules

为防止用户使用脚本刷秒杀接口,限制该接口的调用频率。

具体配置方案:

在stratup文件中添加配置项(目的是读取json信息),具体如下:

 services.AddMemoryCache();       
 services.Configure<ClientRateLimitOptions>(_appConfiguration.GetSection("ClientRateLimiting"));

 // inject counter and rules stores
 services.AddSingleton<IClientPolicyStore, MemoryCacheClientPolicyStore>();
 services.AddSingleton<IRateLimitCounterStore, MemoryCacheRateLimitCounterStore>();

在appsettings.json中添加配置信息

"ClientRateLimiting": {
		"EnableEndpointRateLimiting": true,
		"StackBlockedRequests": false,
		"ClientIdHeader": "X-ClientId",
		"HttpStatusCode": 429,
		"EndpointWhitelist": [],
		"ClientWhitelist": [],
		"GeneralRules": [
			{
				"Endpoint": "get:/api/services/app/CountActivity/GetCountActivity",
				"Period": "1s",
				"Limit": 1
			},
			{
				"Endpoint": "get:/api/services/app/RawData/GetAllUserActivity",
				"Period": "1m",
				"Limit": 10
			}
		]
	}

其中

Endpoint  api

"Period": 时间间隔,
"Limit": 限制次数

Redis缓存

参考资料:https://github.com/StackExchange/StackExchange.Redis

使用redis缓存中的incr(计数器)控制领取次数,计数器作为一个开关,每次用户成功领取一个任务,计数器加1,当达到领取上线时,阻断所有的领任务接口。

注意,在设计缓存连接redis的数据库时,需要保证该类是单例模式,防止每次访问api都去创建redis数据库连接,造成redis的负载过高。

具体代码实现是:

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using Abp.Dependency;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using StackExchange.Redis;

namespace AElf.OfficialSite.StackExchange
{
    public class RedisCacheDatabaseProvider : IRedisCacheDatabaseProvider, ISingletonDependency
    {
        private readonly IDatabase _database;

        public RedisCacheDatabaseProvider(IConfigurationRoot config)
        {
            var connectionMultiplexer = ConnectionMultiplexer.Connect(config["Abp:RedisCache:ConnectionString"]);
            _database = connectionMultiplexer.GetDatabase(2);
        }

        public async Task<bool> SortedSetAddAsync(RedisKey key, RedisValue member, double score, When when = When.Always, CommandFlags flags = CommandFlags.None)
        {
            return await _database.SortedSetAddAsync(key, member, score, when, flags);
        }

        public async Task<long> StringIncrementAsync(RedisKey key, long value = 1, CommandFlags flags = CommandFlags.None)
        {
            return await _database.StringIncrementAsync(key, value, flags);
        }

        public void KeyExpire(RedisKey key, TimeSpan? expiry, CommandFlags flags = CommandFlags.None)
        {
            _database.KeyExpire(key, expiry, flags);
        }

    }
}

注意连接数据库的操作只做一次,千万不能写在方法里,之前因为写在方法里,每次调用api时都去产生新的redis数据库连接,造成线上负载过高,直接宕机了。

API层面的开关

  //counter
  var counter = await _redisCacheDatabaseProvider.StringIncrementAsync($"key-{key}"); ;
  if (counter > input.ActivityTarget)
      return false;

里只是简写计数器,事实上,为保障后续入队是一个用户只产生一次job执行事件,缓存中也会记录用户的点击事件信息

消息队列

用户层面的抢任务,并不是直接与数据库相连了,否则会造成数据库连接访问量过高的问题,而是借助消息队列,通过开关的用户,将直接进入消息队列,等待worker执行任务,完成最后与数据库的插入操作。

await _jobManager.EnqueueAsync<CountActivityBackgroundJob, List<string>>(parameters);


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值