1.缓存概念
1.什么是缓存
这里要讲到的缓存是服务端缓存,简单的说,缓存就是将一些实时性不高,但访问又十分频繁,大概说要很长时间才能取到的数据给存在内存当中,当有请求时直接返回,不消颠末数据库或接口获取。这样就可以减轻数据库的负担。
2.为什么要用缓存
总的来说就是为了进步相应速率(用户体验度),淘汰数据库访问频率。
在一个用户看来,软件利用的体验度才是关键,在对实时性要求不高的情况下,用户肯定会以为打开界面的相应速率快,能包管寻常工作的应用才是好的。因此为了满足这个需求,通过利用缓存,就可以包管满足在正常工作的条件下相应时间尽大概短。
例如:当客户端向服务器请求某个数据时,服务器先在缓存中找,如果在缓存中,就直接返回,无需查询数据库;如果请求的数据不在缓存中,这时再去数据库中找,找到后返回给客户端,并将这个资源参加缓存中。这样下次请求相同资源时,就不需
要毗连数据库了。而且如果把缓存放在内存中,由于对内存的操纵要比对数据库操纵快得多,这样请求时间也会缩短。每当数据发生变革的时候(好比,数据有被修改,或被删除的情况下),要同步的更新缓存信息,确保用户不会在缓存取到旧的数据。
如果没有利用缓存,用户去请求某个数据,当用户量和数据渐渐增长的时候,就会发现每次用户请求的时间越来越长,且数据库无时不刻都在工作。这样用户和数据库都很痛楚,时间一长,就有大概发生下以下事变:
1.用户常诉苦应用打开速率太慢,页面经常无相应,偶尔还会出现崩溃的情况。
2.数据库毗连数满大概说数据库相应慢(处理不外来)。
3.当并发量上来的时候,大概会导致数据库崩溃,使得应用无法正常利用。
2.选用Redis还是Memcached
简单说下这两者的区别,两者都是通过key-value的方式举行存储的,Memcached只有简单的字符串格式,而Redis还支持更多的格式(list、 set、sorted set、hash table ),缓存时利用到的数据都是在内存当中,
不同的在于Redis支持持久化,集群、简单事务、发布/订阅、主从同步等功能,当断电或软件重启时,Memcached中的数据就已经不存在了,而Redis可以通过读取磁盘中的数据再次利用。
这里进步Windows版的安装包:传送门 可视化工具:因文件太大无法上传到博客园。代码仓库中有,需要的私信哦~
3.两者在NetCore 中的利用
Memcached的利用还是相当简单的,首先在 Startup 类中做以下更改,添加缓存参数 赋值给外部类来方便利用
- public void Configure(IApplicationBuilder app, IHostingEnvironment env, IMemoryCache memoryCache)
- {
- //.... 省略部门代码
- DemoWeb.MemoryCache = memoryCache;
- //.... 省略部门代码
- }
复制代码
- public class DemoWeb
- {
-
- //....省略部门代码
- /// <summary>
- /// MemoryCache
- /// </summary>
- public static IMemoryCache MemoryCache { get; set; }
- /// <summary>
- /// 获取当前请求客户端IP
- /// </summary>
- /// <returns></returns>
- public static string GetClientIp()
- {
- var ip = HttpContext.Request.Headers["X-Forwarded-For"].FirstOrDefault()?.Split(',')[0].Trim();
- if (string.IsNullOrEmpty(ip))
- {
- ip = HttpContext.Connection.RemoteIpAddress.ToString();
- }
- return ip;
- }
- }
复制代码
然后创建 MemoryCache 来封装些缓存的简单方法
- /// <summary>
- /// MemoryCache缓存
- /// </summary>
- public class MemoryCache
- {
- private static readonly HashSet<string> Keys = new HashSet<string>();
- /// <summary>
- /// 缓存前缀
- /// </summary>
- public string Prefix { get; }
- /// <summary>
- /// 构造函数
- /// </summary>
- /// <param name="prefix"></param>
- public MemoryCache(string prefix)
- {
- Prefix = prefix + "_";
- }
- /// <summary>
- /// 获取
- /// </summary>
- /// <typeparam name="T"></typeparam>
- /// <param name="key"></param>
- /// <returns></returns>
- public T Get<T>(string key)
- {
- return DemoWeb.MemoryCache.Get<T>(Prefix + key);
- }
- /// <summary>
- /// 设置 无逾期时间
- /// </summary>
- /// <param name="key"></param>
- /// <param name="data"></param>
- public void Set(string key, object data)
- {
- key = Prefix + key;
- DemoWeb.MemoryCache.Set(key, data);
- if (!Keys.Contains(key))
- {
- Keys.Add(key);
- }
- }
- /// <summary>
- /// 设置
- /// </summary>
- /// <param name="key"></param>
- /// <param name="data"></param>
- /// <param name="absoluteExpiration"></param>
- public void Set(string key, object data, DateTimeOffset absoluteExpiration)
- {
- key = Prefix + key;
- DemoWeb.MemoryCache.Set(key, data, absoluteExpiration);
- if (!Keys.Contains(key))
- {
- Keys.Add(key);
- }
- }
- /// <summary>
- /// 设置
- /// </summary>
- /// <param name="key"></param>
- /// <param name="data"></param>
- /// <param name="absoluteExpirationRelativeToNow"></param>
- public void Set(string key, object data, TimeSpan absoluteExpirationRelativeToNow)
- {
- key = Prefix + key;
- DemoWeb.MemoryCache.Set(key, data, absoluteExpirationRelativeToNow);
- if (!Keys.Contains(key))
- {
- Keys.Add(key);
- }
- }
- /// <summary>
- /// 设置
- /// </summary>
- /// <param name="key"></param>
- /// <param name="data"></param>
- /// <param name="expirationToken"></param>
- public void Set(string key, object data, IChangeToken expirationToken)
- {
- key = Prefix + key;
- DemoWeb.MemoryCache.Set(key, data, expirationToken);
- if (!Keys.Contains(key))
- {
- Keys.Add(key);
- }
- }
- /// <summary>
- /// 设置
- /// </summary>
- /// <param name="key"></param>
- /// <param name="data"></param>
- /// <param name="options"></param>
- public void Set(string key, object data, MemoryCacheEntryOptions options)
- {
- key = Prefix + key;
- DemoWeb.MemoryCache.Set(key, data, options);
- if (!Keys.Contains(key))
- {
- Keys.Add(key);
- }
- }
- /// <summary>
- /// 移除某个
- /// </summary>
- /// <param name="key"></param>
- public void Remove(string key)
- {
- key = Prefix + key;
- DemoWeb.MemoryCache.Remove(key);
- if (Keys.Contains(key))
- {
- Keys.Remove(key);
- }
- }
- /// <summary>
- /// 清空全部
- /// </summary>
- public void ClearAll()
- {
- foreach (var key in Keys)
- {
- DemoWeb.MemoryCache.Remove(key);
- }
- Keys.Clear();
- }
- }
复制代码
View Code
其实接下来就可以直接利用缓存了,但为了方便利用,再建一个缓存类别的中间类来管理。
- public class UserCache
- {
- private static readonly MemoryCache Cache = new MemoryCache("User");
- private static TimeSpan _timeout = TimeSpan.Zero;
- private static TimeSpan Timeout
- {
- get
- {
- if (_timeout != TimeSpan.Zero)
- return _timeout;
- try
- {
- _timeout = TimeSpan.FromMinutes(20);
- return _timeout;
- }
- catch (Exception)
- {
- return TimeSpan.FromMinutes(10);
- }
- }
- }
- public static void Set(string key,string cache)
- {
- if (string.IsNullOrEmpty(cache))
- return;
- Cache.Set(key, cache, Timeout);
- }
- public static string Get(string key)
- {
- if (string.IsNullOrEmpty(key))
- return default(string);
- return Cache.Get<string>(key);
- }
- }
复制代码
UserCache
测试是否可以正常利用:代码与截图
- [HttpGet]
- [Route("mecache")]
- public ActionResult ValidToken()
- {
- var key = "tkey";
- UserCache.Set(key, "测试数据");
- return Succeed(UserCache.Get(key));
- }
复制代码
可以清楚的看到 MemoryCache 可以正常利用。
那么接下来将讲到如何利用 Redis 缓存。先在需要封装基础类的项目 Nuget 包中添加 StackExchange.Redis 依靠。然后添加Redis 毗连类
- internal class RedisConnectionFactory
- {
- public string ConnectionString { get; set; }
- public string Password { get; set; }
- public ConnectionMultiplexer CurrentConnectionMultiplexer { get; set; }
- /// <summary>
- /// 设置毗连字符串
- /// </summary>
- /// <returns></returns>
- public void SetConnectionString(string connectionString)
- {
- ConnectionString = connectionString;
- }
- /// <summary>
- /// 设置毗连字符串
- /// </summary>
- /// <returns></returns>
- public void SetPassword(string password)
- {
- Password = password;
- }
- public ConnectionMultiplexer GetConnectionMultiplexer()
- {
- if (CurrentConnectionMultiplexer == null || !CurrentConnectionMultiplexer.IsConnected)
- {
- if (CurrentConnectionMultiplexer != null)
- {
- CurrentConnectionMultiplexer.Dispose();
- }
- CurrentConnectionMultiplexer = GetConnectionMultiplexer(ConnectionString);
- }
- return CurrentConnectionMultiplexer;
- }
- private ConnectionMultiplexer GetConnectionMultiplexer(string connectionString)
- {
- ConnectionMultiplexer connectionMultiplexer;
- if (!string.IsNullOrWhiteSpace(Password) && !connectionString.ToLower().Contains("password"))
- {
- connectionString += $",password={Password}";
- }
- var redisConfiguration = ConfigurationOptions.Parse(connectionString);
- redisConfiguration.AbortOnConnectFail = true;
- redisConfiguration.AllowAdmin = false;
- redisConfiguration.ConnectRetry = 5;
- redisConfiguration.ConnectTimeout = 3000;
- redisConfiguration.DefaultDatabase = 0;
- redisConfiguration.KeepAlive = 20;
- redisConfiguration.SyncTimeout = 30 * 1000;
- redisConfiguration.Ssl = false;
- connectionMultiplexer = ConnectionMultiplexer.Connect(redisConfiguration);
- return connectionMultiplexer;
- }
- }
复制代码
RedisConnectionFactory
再添加Redis客户端类
- /// <summary>
- /// Redis Client
- /// </summary>
- public class RedisClient : IDisposable
- {
- public int DefaultDatabase { get; set; } = 0;
- private readonly ConnectionMultiplexer _client;
- private IDatabase _db;
- public RedisClient(ConnectionMultiplexer client)
- {
- _client = client;
- UseDatabase();
- }
- public void UseDatabase(int db = -1)
- {
- if (db == -1)
- db = DefaultDatabase;
- _db = _client.GetDatabase(db);
- }
- public string StringGet(string key)
- {
- return _db.StringGet(key).ToString();
- }
- public void StringSet(string key, string data)
- {
- _db.StringSet(key, data);
- }
- public void StringSet(string key, string data, TimeSpan timeout)
- {
- _db.StringSet(key, data, timeout);
- }
- public T Get<T>(string key)
- {
- var json = StringGet(key);
- if (string.IsNullOrEmpty(json))
- {
- return default(T);
- }
- return json.ToNetType<T>();
- }
- public void Set(string key, object data)
- {
- var json = data.ToJson();
- _db.StringSet(key, json);
- }
- public void Set(string key, object data, TimeSpan timeout)
- {
- var json = data.ToJson();
- _db.StringSet(key, json, timeout);
- }
- /// <summary>
- /// Exist
- /// </summary>
- /// <param name="key"></param>
- /// <returns></returns>
- public bool Exist(string key)
- {
- return _db.KeyExists(key);
- }
- /// <summary>
- /// Delete
- /// </summary>
- /// <param name="key"></param>
- /// <returns></returns>
- public bool Delete(string key)
- {
- return _db.KeyDelete(key);
- }
- /// <summary>
- /// Set Expire to Key
- /// </summary>
- /// <param name="key"></param>
- /// <param name="expiry"></param>
- /// <returns></returns>
- public bool Expire(string key, TimeSpan? expiry)
- {
- return _db.KeyExpire(key, expiry);
- }
- /// <summary>
- /// 计数器 如果不存在则设置值,如果存在则添加值 如果key存在且类型不为long 则会异常
- /// </summary>
- /// <param name="key"></param>
- /// <param name="value"></param>
- /// <param name="expiry">只有第一次设置有用期见效</param>
- /// <returns></returns>
- public long SetStringIncr(string key, long value = 1, TimeSpan? expiry = null)
- {
- var nubmer = _db.StringIncrement(key, value);
- if (nubmer == 1 && expiry != null)//只有第一次设置有用期(防止覆盖)
- _db.KeyExpireAsync(key, expiry);//设置有用期
- return nubmer;
- }
- /// <summary>
- /// 读取计数器
- /// </summary>
- /// <param name="key"></param>
- /// <returns></returns>
- public long GetStringIncr(string key)
- {
- var value = StringGet(key);
- return string.IsNullOrWhiteSpace(value) ? 0 : long.Parse(value);
- }
- /// <summary>
- /// 计数器-淘汰 如果不存在则设置值,如果存在则淘汰值 如果key存在且类型不为long 则会异常
- /// </summary>
- /// <param name="key"></param>
- /// <returns></returns>
- public long StringDecrement(string key, long value = 1)
- {
- var nubmer = _db.StringDecrement(key, value);
- return nubmer;
- }
- public void Dispose()
- {
- _client?.Dispose();
- }
- }
复制代码
RedisClient
然后再添加Redis毗连生成工具类
- public static class RedisFactory
- {
- private static readonly object Locker = new object();
- private static RedisConnectionFactory factory;
- private static void InitRedisConnection()
- {
- try
- {
- factory = new RedisConnectionFactory();
- var connectionString = DemoWeb.Configuration["Redis:ConnectionString"];
- #if DEBUG
- connectionString = "127.0.0.1:6379";
- #endif
- factory.ConnectionString = connectionString;
- factory.Password = DemoWeb.Configuration["Redis:Pwd"];
- }
- catch (Exception e)
- {
- LogHelper.Logger.Fatal(e, "Redis毗连创建失败。");
- }
- }
- public static RedisClient GetClient()
- {
- //先判定一轮,淘汰锁,进步服从
- if (factory == null || string.IsNullOrEmpty(factory.ConnectionString))
- {
- //防止并发创建
- lock (Locker)
- {
- InitRedisConnection();
- }
- }
- return new RedisClient(factory.GetConnectionMultiplexer())
- {
- DefaultDatabase = DemoWeb.Configuration["Redis:DefaultDatabase"].ToInt()
- };
- }
- }
复制代码
RedisFactory
这里要利用到前面的静态扩展方法。请自行添加 传送门 ,还需要将 Startup 类中的 Configuration 给赋值到 DemoWeb中的 Configuration 字段值来利用。
在配置文件 appsettings.json 中添加
- "Redis": {
- "ConnectionString": "127.0.0.1:6379",
- "Pwd": "",
- "DefaultDatabase": 0
- }
复制代码
再添加Redis缓存利用类
- /// <summary>
- /// Redis缓存
- /// </summary>
- public class RedisCache
- {
- private static RedisClient _client;
- private static RedisClient Client => _client ?? (_client = RedisFactory.GetClient());
- private static string ToKey(string key)
- {
- return $"Cache_redis_{key}";
- }
- /// <summary>
- /// 获取
- /// </summary>
- /// <typeparam name="T"></typeparam>
- /// <param name="key"></param>
- /// <returns></returns>
- public static T Get<T>(string key)
- {
- try
- {
- var redisKey = ToKey(key);
- return Client.Get<T>(redisKey);
- }
- catch (Exception e)
- {
- LogHelper.Logger.Fatal(e, "RedisCache.Get \n key:{0}", key);
- return default(T);
- }
- }
- /// <summary>
- /// 尝试获取
- /// </summary>
- /// <typeparam name="T"></typeparam>
- /// <param name="key"></param>
- /// <param name="result"></param>
- /// <returns></returns>
- private static T TryGet<T>(string key, out bool result)
- {
- result = true;
- try
- {
- var redisKey = ToKey(key);
- return Client.Get<T>(redisKey);
- }
- catch (Exception e)
- {
- LogHelper.Logger.Fatal(e, "RedisCache.TryGet \n key:{0}", key);
- result = false;
- return default(T);
- }
- }
- /// <summary>
- /// 获取
- /// </summary>
- /// <typeparam name="T"></typeparam>
- /// <param name="key"></param>
- /// <param name="setFunc"></param>
- /// <param name="expiry"></param>
- /// <param name="resolver"></param>
- /// <returns></returns>
- public static T Get<T>(string key, Func<T> setFunc, TimeSpan? expiry = null)
- {
- var redisKey = ToKey(key);
- var result = TryGet<T>(redisKey, out var success);
- if (success && result == null)
- {
- result = setFunc();
- try
- {
- Set(redisKey, result, expiry);
- }
- catch (Exception e)
- {
- LogHelper.Logger.Fatal(e, "RedisCache.Get<T> \n key:{0}", key);
- }
- }
- return result;
- }
- /// <summary>
- /// 设置
- /// </summary>
- /// <typeparam name="T"></typeparam>
- /// <param name="key"></param>
- /// <param name="value"></param>
- /// <param name="expiry"></param>
- /// <returns></returns>
- public static bool Set<T>(string key, T value, TimeSpan? expiry = null)
- {
- var allRedisKey = ToKey("||Keys||");
- var redisKey = ToKey(key);
- var allkeyRedisValue = Client.StringGet(allRedisKey);
- var keys = allkeyRedisValue.ToNetType<List<string>>() ?? new List<string>();
- if (!keys.Contains(redisKey))
- {
- keys.Add(redisKey);
- Client.Set(allRedisKey, keys);
- }
- if (expiry.HasValue)
- {
- Client.StringSet(redisKey, value.ToJson(), expiry.Value);
- }
- else
- {
- Client.StringSet(redisKey, value.ToJson());
- }
- return true;
- }
- /// <summary>
- /// 重新设置逾期时间
- /// </summary>
- /// <param name="key"></param>
- /// <param name="expiry"></param>
- public static void ResetItemTimeout(string key, TimeSpan expiry)
- {
- var redisKey = ToKey(key);
- Client.Expire(redisKey, expiry);
- }
- /// <summary>
- /// Exist
- /// </summary>
- /// <param name="key">原始key</param>
- /// <returns></returns>
- public static bool Exist(string key)
- {
- var redisKey = ToKey(key);
- return Client.Exist(redisKey);
- }
- /// <summary>
- /// 计数器 增长 能设置逾期时间的都设置逾期时间
- /// </summary>
- /// <param name="key"></param>
- /// <param name="value"></param>
- /// <param name="expiry"></param>
- /// <returns></returns>
- public static bool SetStringIncr(string key, long value = 1, TimeSpan? expiry = null, bool needRest0 = false)
- {
- var redisKey = ToKey(key);
- try
- {
- if (expiry.HasValue)
- {
- if (Exist(key) && needRest0)
- {
- var exitValue = GetStringIncr(key);
- Client.SetStringIncr(redisKey, value - exitValue, expiry.Value);
- }
- else
- {
- Client.SetStringIncr(redisKey, value, expiry.Value);
- }
- }
- else
- {
- if (Exist(key) && needRest0)
- {
- var exitValue = GetStringIncr(key);
- Client.SetStringIncr(redisKey, value - exitValue);
- }
- else
- {
- Client.SetStringIncr(redisKey, value);
- }
- }
- }
- catch (Exception e)
- {
- LogHelper.Logger.Fatal($"计数器-增长错误,缘故起因:{e.Message}");
- return false;
- }
- return true;
- }
- /// <summary>
- /// 读取计数器
- /// </summary>
- /// <param name="key"></param>
- /// <returns></returns>
- public static long GetStringIncr(string key)
- {
- var redisKey = ToKey(key);
- return Client.GetStringIncr(redisKey);
- }
- /// <summary>
- /// 计数器 - 淘汰
- /// </summary>
- /// <param name="key"></param>
- /// <returns></returns>
- public static bool StringDecrement(string key, long value = 1)
- {
- var redisKey = ToKey(key);
- try
- {
- Client.StringDecrement(redisKey, value);
- return true;
- }
- catch (Exception e)
- {
- LogHelper.Logger.Fatal($"计数器-淘汰错误,缘故起因:{e.Message}");
- return false;
- }
- }
- /// <summary>
- /// 删除
- /// </summary>
- /// <param name="key"></param>
- /// <returns></returns>
- public static bool Delete(string key)
- {
- var redisKey = ToKey(key);
- return Client.Delete(redisKey);
- }
- /// <summary>
- /// 清空
- /// </summary>
- public static void Clear()
- {
- //由于codis不支持keys之类的下令,以是只能本身记载下来,然后通过这个来整理。
- var redisKey = ToKey("||Keys||");
- var keys = Client.Get<List<string>>(redisKey);
- var notExists = new List<string>();
- foreach (var key in keys)
- {
- if (Client.Exist(key))
- Client.Delete(key);
- else
- notExists.Add(key);
- }
- if (notExists.Count > 0)
- {
- keys.RemoveAll(s => notExists.Contains(s));
- Client.Set(redisKey, keys);
- }
- }
- }
复制代码
RedisCache
到这来根本就快可以拿来测试是否可以用了。但是条件是得把 Redis 给运行起来。
将上面的 Redis安装包安装,并启动所安装文件夹中的 redis-server.exe 步伐,若出现闪退情况,就运行 redis-cli.exe 步伐,然后输入 shutdown 按下回车,重新运行 redis-server.exe 步伐,就会出现这个界面。
到这来,添加一个测试方法来看看结果。借助Redis可视化工具检察结果如下
测试完善成功,由于时间题目,上面 RedisCache只有字符串的方法,没有加别的类型的方法。有需要的本身加咯~
在下一篇中将先容如何在NetCore中如何利用 过滤器来举行权限验证
有需要源码的在下方评论或私信~给我的SVN访客账户暗码下载,代码未放在GitHub上。svn中新加上了Redis安装包及可视化工具。
来源:https://www.cnblogs.com/levywang/archive/2019/09/11/coreframe_8.html |