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>();
![](https://i-blog.csdnimg.cn/blog_migrate/45f2d96919378aecd3b223c18cc729d1.png)
在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);