• 从零开始搭建前后端分离的NetCore2.2(EF Core CodeFirst+Autofac)+Vue的项目框架之八MemoryCache与redis缓存的使用


     

     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;
                //....  省略部分代码
            }    
    DemoWeb中的代码:
     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 
     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 
     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> 
     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中如何使用 过滤器来进行权限验证

      有需要源码的可通过此 GitHub 链接拉取 觉得还可以的给个 start 哦,谢谢!svn中新加上了Redis安装包及可视化工具。

  • 相关阅读:
    串口基本知识
    20180826
    20180819
    自动化测试
    说话有重点 测试思维
    学习C语言,在软件测试中如何用?
    PC能替代服务器吗?
    服务器与普通电脑的区别?
    k8s 回滚应用
    k8s Service
  • 原文地址:https://www.cnblogs.com/levywang/p/coreframe_8.html
Copyright © 2020-2023  润新知