C# Web服务同一客户端调用次数跟踪方案咨询(非数据库)
针对你的场景,我整理了几个基于C#原生机制的解决方案,不需要依赖数据库,还支持自动过期,适配不同的应用重启情况:
方案1:进程内内存缓存(适用于应用不重启的情况)
如果你的“应用从头重启”只是指方法内部的逻辑重置,而非整个进程/应用域重启,那么用.NET自带的System.Runtime.Caching.MemoryCache是最简便的选择——它是进程内的内存缓存,支持绝对过期和滑动过期,完全满足你的临时存储需求。
代码示例:
using System.Runtime.Caching; public class dosomething { // 使用默认的内存缓存实例 private static readonly ObjectCache _cache = MemoryCache.Default; // 如果需要区分不同客户端,这里的键要换成客户端唯一标识(比如IP、请求ID等) private const string ClientCounterKey = "DefaultClientCallCounter"; public Response somemethod() { // 读取计数器,不存在则初始化为0 int counter = _cache.Get(ClientCounterKey) as int? ?? 0; int someLimit = 3; // 自定义的调用次数限制 if(counter > someLimit) { return new Response("go away"); } // 更新计数器,设置10分钟绝对过期,也可以加滑动过期 var cachePolicy = new CacheItemPolicy { AbsoluteExpiration = DateTimeOffset.Now.AddMinutes(10), // 可选:滑动过期,每次调用刷新过期时间 SlidingExpiration = TimeSpan.FromMinutes(5) }; _cache.Set(ClientCounterKey, counter + 1, cachePolicy); Response response = new Response(new Link("www.imawebservice.com/dosomething.svc/somemethod")); return response; } }
注意:如果每次调用真的会导致整个应用进程重启,这个方案就失效了——因为内存缓存会随进程销毁而清空。
方案2:本地文件存储(适用于进程重启的情况)
如果每次调用都会触发进程重启,那必须用持久化存储。可以用.NET原生的文件操作来实现,配合简单的JSON格式存储键值对和过期时间,同时加锁避免并发冲突。
代码示例:
using System.IO; using System.Text.Json; public class dosomething { // 缓存文件路径,建议放在应用的临时目录或专用缓存目录 private const string CacheFile = @"C:\Temp\WebServiceCallCounters.json"; private const string ClientCounterKey = "DefaultClientCallCounter"; // 文件锁,确保多线程/多进程写入时的线程安全 private static readonly object _fileLock = new object(); public Response somemethod() { // 读取并解析缓存文件 var cacheData = new Dictionary<string, CacheEntry>(); lock(_fileLock) { if(File.Exists(CacheFile)) { string jsonContent = File.ReadAllText(CacheFile); cacheData = JsonSerializer.Deserialize<Dictionary<string, CacheEntry>>(jsonContent) ?? new Dictionary<string, CacheEntry>(); } } // 清理过期的缓存项 DateTime now = DateTime.Now; List<string> expiredKeys = cacheData.Where(kv => kv.Value.ExpiresAt < now).Select(kv => kv.Key).ToList(); foreach(string key in expiredKeys) { cacheData.Remove(key); } // 获取当前计数器值 int counter = cacheData.TryGetValue(ClientCounterKey, out CacheEntry entry) ? entry.CounterValue : 0; int someLimit = 3; if(counter > someLimit) { return new Response("go away"); } // 更新计数器,设置10分钟过期 cacheData[ClientCounterKey] = new CacheEntry { CounterValue = counter + 1, ExpiresAt = now.AddMinutes(10) }; // 写入更新后的缓存数据到文件 lock(_fileLock) { string jsonOutput = JsonSerializer.Serialize(cacheData); File.WriteAllText(CacheFile, jsonOutput); } Response response = new Response(new Link("www.imawebservice.com/dosomething.svc/somemethod")); return response; } // 辅助类,存储计数器值和过期时间 private class CacheEntry { public int CounterValue { get; set; } public DateTime ExpiresAt { get; set; } } }
注意:要确保应用对目标文件路径有读写权限;如果是多实例部署,多个进程读写同一文件可能会有性能问题,需要额外的分布式同步机制。
方案3:Windows注册表存储(仅适用于Windows环境,进程重启的情况)
如果你的服务运行在Windows环境下,可以用.NET原生的Microsoft.Win32.Registry类来存储计数器,同样能实现过期逻辑,不需要额外的文件操作。
代码示例:
using Microsoft.Win32; public class dosomething { // 注册表路径,建议放在当前用户的注册表分支下(权限要求更低) private const string RegistrySubPath = @"Software\MyWebService\CallCounters"; private const string CounterValueName = "DefaultClientCounter"; private const string ExpirationValueName = "DefaultClientCounter_Expires"; public Response somemethod() { int counter = 0; DateTime expiresAt = DateTime.MinValue; // 读取注册表中的计数器和过期时间 using(RegistryKey key = Registry.CurrentUser.OpenSubKey(RegistrySubPath, true)) { if(key != null) { counter = (int?)key.GetValue(CounterValueName, 0) ?? 0; string expStr = key.GetValue(ExpirationValueName, DateTime.MinValue.ToString()).ToString(); DateTime.TryParse(expStr, out expiresAt); } } // 检查是否过期,过期则重置计数器 if(DateTime.Now > expiresAt && expiresAt != DateTime.MinValue) { counter = 0; } int someLimit = 3; if(counter > someLimit) { return new Response("go away"); } // 更新计数器和过期时间(10分钟) DateTime newExpiresAt = DateTime.Now.AddMinutes(10); using(RegistryKey key = Registry.CurrentUser.CreateSubKey(RegistrySubPath)) { key?.SetValue(CounterValueName, counter + 1); key?.SetValue(ExpirationValueName, newExpiresAt.ToString()); } Response response = new Response(new Link("www.imawebservice.com/dosomething.svc/somemethod")); return response; } }
注意:这个方案仅支持Windows系统,且需要应用有注册表的读写权限;非Windows环境请用方案2。
额外提示
- 客户端区分:如果需要跟踪多个客户端的调用次数,你需要获取客户端的唯一标识(比如从
HttpContext中读取客户端IP、请求头中的唯一ID等),将其作为缓存的键,避免不同客户端的计数器相互干扰。 - 并发安全:所有涉及持久化的操作都要加锁,防止多线程/多进程同时读写导致的数据错乱。
内容的提问来源于stack exchange,提问作者Lixyna
相关产品推荐
相关产品推荐

