NET 5 MemoryCache与Redis使用以及StackExchange.Redis和CSRedisCore

简介以及区别

ASP.NET Core 缓存Caching,.NET Core 中为我们提供了Caching 的组件。

目前Caching 组件提供了三种存储方式。

Memory

Redis

SqlServer

1.MemoryCache

Cache是一个绝大多数项目会用到的一个技术 为了减少磁盘的读取次数,提高程序性能,将频繁读取的配置文件缓存到内存中,加速配置的读取。并且,在磁盘的配置文件更改后,更新缓存

  1. 绝对过期支持
  2. 滑动过期支持(指定一个时间,TimeSpan,指定时间内有被Get缓存时间则顺延,否则过期)
  3. 过期回调
  4. 自定义过期

仓库地址是:https://github.com/aspnet/Caching

IMemoryCache,它表示存储在 Web 服务器内存中的缓存,内存缓存可以存储任何对象,存储形式键值对

Memorycache无法进行持久化

Memorycache只支持简单的key/value数据结构

Memorycache 是多线程  速度快

2.Redis

简单来说 redis 就是一个数据库,不过与传统数据库不同的是 redis 的数据是存在内存中的,所以读写速度非常快,因此 redis 被广泛应用于缓存方向。另外,redis 也经常用来做分布式锁。redis 提供了多种数据类型来支持不同的业务场景。除此之外,redis 支持事务 、持久化、LUA脚本、LRU驱动事件、多种集群方案。

Redis是一个支持持久化的内存数据库,通过持久化机制把内存中的数据同步到硬盘文件来保证数据持久化 ,当Redis重启后通过把硬盘文件重新加载到内存,就能达到恢复数据的目的。

如果你对数据持久化和数据同步有所要求,那么推荐你选择Redis,因为这两个特性Memcached都不具备。即使你只是希望在升级或者重启系统后缓存数据不会丢失,选择Redis也是明智的。

Redis支持数据类型比如key-value string  list hash zset 等

Redis缓存数据库(多个)   Redis缓存文件夹(多个目录)

Redis只能使用单线程,性能受限于CPU性能

区别

对于 redis 和 memcached 我总结了下面四点。现在公司一般都是用 redis 来实现缓存,而且 redis 自身也越来越强大了!

  1、redis支持更丰富的数据类型(支持更复杂的应用场景):Redis不仅仅支持简单的k/v类型的数据,同时还提供list,set,zset,hash等数据结构的存储。memcache支持简单的数据类型,String。

  2、Redis支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用,而Memecache把数据全部存在内存之中。

  3、集群模式:memcached没有原生的集群模式,需要依靠客户端来实现往集群中分片写入数据;但是 redis 目前是原生支持 cluster 模式的.

  4、Memcached是多线程,非阻塞IO复用的网络模型;Redis使用单线程的多路 IO 复用模型。

应用场景

redis:数据量较小的更性能操作和运算上。

memcache:用于在动态系统中减少数据库负载,提升性能;做缓存,提高性能(适合读多写少,对于数据量比较大,可以采用sharding)。

1.MemoryCache使用

Session缓存和Cache缓存的区别如下:

(1)最大的区别是Cache提供缓存依赖来更新数据,而Session只能依靠定义的缓存时间来判断缓存数据是否有效。

(2)即使应用程序终止,只要Cache.Add方法中定义的缓存时间未过期,下次开启应用程序时,缓存的数据依然存在。而Session缓存只是存在于一次会话中,会话结束后,数据也就失效了。

(3)Session容易丢失,导致数据的不确定性,而Cache不会出现这种情况。

(4)由于Session是每次会话就被加载,所以不适宜存放大量信息,否则会导致服务器的性能降低。而Cache则主要用来保存大容量信息,如数据库中的多个表。

需要特别注意:为了提高Cache的有效利用率,建议对于不经常改动的数据使用Cache。

public void ConfigureServices(IServiceCollection services)
{  
    //如何处理session
   services.AddSession();  
    //memoryCache   
   services.AddMemoryCache();
   
    //.......
}

 public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    //启用session
    app.UseSession();
    app.UseRouting();
    //......
}

 

 

 

public void ConfigureServices(IServiceCollection services)
        {
            services.AddMemoryCache();
            // Add framework services.
                  
        }
private IMemoryCache _cache;

        public LongLongController(IMemoryCache memoryCache)
        {
            _cache = memoryCache;
        }

1、方法:TryGetValue 及 方法缓存的存取(在TryGetValue 中,Out 参数的类型应与缓存中存储的值的类型一致。否则TryGetValue 中的Out参数永远为NULL。)

public IActionResult Index()
        {
            string cacheKey_2 = "CacheKey";
            List<string> cacheEntry;
            //如果缓存没有过期,则Out测试就是缓存存储的值,注意存放值的类型应该和Out参数一致。
            var bol = _cache.TryGetValue<List<string>>(cacheKey_2, out cacheEntry);
            //判断缓存是否存在
            if (!bol)
            {
                List<string> lst = new List<string>() { "陈大六", "陈卧龙", "陈新宇", "刘丽", "花国锋" };
                var cacheEntryOptions = new MemoryCacheEntryOptions()
                 .SetSlidingExpiration(TimeSpan.FromMinutes(10));
                _cache.Set(cacheKey_2, lst, cacheEntryOptions);
            }
            ViewBag.cacheEntry = _cache.Get(cacheKey_2);
            return View();
        }

2、设置缓存的过期时间,可采用绝对过期时间或相对过期时间两种模式;

相对过期时间设置方式:

var cacheEntryOptions = new MemoryCacheEntryOptions()
                 .SetSlidingExpiration(TimeSpan.FromSeconds(10));

注意上述代码中的备注;假设我们设置一个缓存的相对过期时间为10秒,缓存由A创建,十秒中内,B进入可系统,并读取了缓存,这时缓存的有效时间又会变成十秒,同理,只要缓存在十秒钟内不间断有其他人访问,则缓存永远不会过期。如果大于十秒,则缓存过期。

绝对过期时间设置方式:

 var cacheEntryOptions = new MemoryCacheEntryOptions()
                 .SetAbsoluteExpiration(TimeSpan.FromSeconds(10));

绝对过期时间不管期间有没有人访问,在时间过后,就会过期,清空。

3、移除缓存

_cache.Remove(cacheKey_2);

4、缓存的优先级分为四种:永不过期  大于  高优先级  大于  一般优先级  大于  低优先级。

/缓存优先级 (程序压力大时,会根据优先级自动回收)
View Code

5、缓存过期后,执行回调函数,

public IActionResult cacheCallback()
        {
            List<string> lst = new List<string>() { "陈大六", "陈卧龙", "陈新宇", "刘丽", "花国锋" };
            //缓存回调 10秒过期会回调
            string cacheKey = "cacheKey";
            _cache.Set(cacheKey, lst, new MemoryCacheEntryOptions()
                .SetAbsoluteExpiration(TimeSpan.FromSeconds(10))
                .RegisterPostEvictionCallback((key, value, reason, substate) =>
                {
                    //调用回调函数
                    GetIntList(key,value,reason,substate);
                }));
            //
            return View();
        }
        public void GetIntList(object key,object value, object reason, object substate)
        {
            List<string> lst=(List<string>)value;
            //说白了就是被释放了
            Console.WriteLine($"键{key}的缓存因:{reason}而改变。");
            foreach(var item in lst)
            {
                Console.WriteLine(item);
            }
        }

6、缓存回调 根据Token过期

public ActionResult CacheToken()
        {
            List<string> lst = new List<string>() { "陈大六", "陈卧龙", "陈新宇", "刘丽", "花国锋" };
            string cacheKey = "CacheToken";
            //缓存回调 根据Token过期
            var cts = new CancellationTokenSource();
            _cache.Set(cacheKey, lst, new MemoryCacheEntryOptions()
                .AddExpirationToken(new CancellationChangeToken(cts.Token))
                .RegisterPostEvictionCallback((key, value, reason, substate) =>
                {
                    Console.WriteLine($"键{key}值{value}改变,因为{reason}");
                }));
            cts.Cancel(); //执行到Cancel()方法时,会执行回调删除
            return View();
        }

2.Redis下载

redis安装包:https://github.com/microsoftarchive/redis/releases

redis客户端:https://redisdesktop.com/download  最新的要付费可以使用旧版

https://github.com/uglide/RedisDesktopManager/releases/tag/0.8.8

为什么要用 redis?/为什么要用缓存?
  主要从“高性能”和“高并发”这两点来看待这个问题。

高性能:

假如用户第一次访问数据库中的某些数据。这个过程会比较慢,因为是从硬盘上读取的。将该用户访问的数据存在缓存中,这样下一次再访问这些数据的时候就可以直接从缓存中获取了。操作缓存就是直接操作内存,所以速度相当快。如果数据库中的对应数据改变的之后,同步改变缓存中相应的数据即可!

高并发:

直接操作缓存能够承受的请求是远远大于直接访问数据库的,所以我们可以考虑把数据库中的部分数据转移到缓存中去,这样用户的一部分请求会直接到缓存这里而不用经过数据库。

redis 的线程模型
  redis 内部使用文件事件处理器 file event handler,这个文件事件处理器是单线程的,所以 redis 才叫做单线程的模型。它采用 IO 多路复用机制同时监听多个 socket,根据 socket 上的事件来选择对应的事件处理器进行处理。

  文件事件处理器的结构包含 4 个部分:

  多个 socket
  IO 多路复用程序
  文件事件分派器
  事件处理器(连接应答处理器、命令请求处理器、命令回复处理器)
  多个 socket 可能会并发产生不同的操作,每个操作对应不同的文件事件,但是 IO 多路复用程序会监听多个 socket,会将 socket 产生的事件放入队列中排队,事件分派器每次从队列中取出一个事件,把该事件交给对应的事件处理器进行处理。

3.StackExchange.Redis

在NuGet上安装StackExchange.Redis,然后在appsettings.json文件里面添加Redis相关配置信息:

{
  "Logging": {
    "LogLevel": {
      "Default": "Warning"
    }
  },
  "AllowedHosts": "*",
  "Redis": {
    "Default": {
      "Connection": "127.0.0.1:6379",
      "InstanceName": "local",
      "DefaultDB": 8
    }
  }
}

帮助类

using StackExchange.Redis;
using System;
using System.Collections.Concurrent;

namespace RedisDemo
{
    public class RedisHelper : IDisposable
    {
        //连接字符串
        private string _connectionString;
        //实例名称
        private string _instanceName;
        //默认数据库
        private int _defaultDB; 
        private ConcurrentDictionary<string, ConnectionMultiplexer> _connections;
        public RedisHelper(string connectionString, string instanceName, int defaultDB = 0)
        {
            _connectionString = connectionString;
            _instanceName = instanceName;
            _defaultDB = defaultDB;
            _connections = new ConcurrentDictionary<string, ConnectionMultiplexer>();
        }

        /// <summary>
        /// 获取ConnectionMultiplexer
        /// </summary>
        /// <returns></returns>
        private ConnectionMultiplexer GetConnect()
        {
            return _connections.GetOrAdd(_instanceName, p => ConnectionMultiplexer.Connect(_connectionString));
        }

        /// <summary>
        /// 获取数据库
        /// </summary>
        /// <param name="configName"></param>
        /// <param name="db">默认为0:优先代码的db配置,其次config中的配置</param>
        /// <returns></returns>
        public IDatabase GetDatabase()
        {
            return GetConnect().GetDatabase(_defaultDB);
        }

        public IServer GetServer(string configName = null, int endPointsIndex = 0)
        {
            var confOption = ConfigurationOptions.Parse(_connectionString);
            return GetConnect().GetServer(confOption.EndPoints[endPointsIndex]);
        }

        public ISubscriber GetSubscriber(string configName = null)
        {
            return GetConnect().GetSubscriber();
        }

        public void Dispose()
        {
            if (_connections != null && _connections.Count > 0)
            {
                foreach (var item in _connections.Values)
                {
                    item.Close();
                }
            }
        }
    }
}

在Startup.cs类的ConfigureServices方法里面添加服务注入:

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;

namespace RedisDemo
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            //redis缓存
            var section = Configuration.GetSection("Redis:Default");
            //连接字符串
            string _connectionString = section.GetSection("Connection").Value;
            //实例名称
            string _instanceName = section.GetSection("InstanceName").Value;
            //默认数据库 
            int _defaultDB = int.Parse(section.GetSection("DefaultDB").Value ?? "0");           
            services.AddSingleton(new RedisHelper(_connectionString, _instanceName, _defaultDB));
            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseMvc();
        }
    }
}

新建一个控制器,然后通过构造函数注入:

using Microsoft.AspNetCore.Mvc;
using StackExchange.Redis;

namespace RedisDemo.Controllers
{
    [Route("api/redis")]
    [ApiController]
    public class RedisController : ControllerBase
    {
        private readonly IDatabase _redis;
        public RedisController(RedisHelper client)
        {
            _redis = client.GetDatabase();
        }

        [HttpGet]
        public string Get()
        {
            // 往Redis里面存入数据
            _redis.StringSet("Name", "Tom");
            // 从Redis里面取数据
            string name = _redis.StringGet("Name");
            return name;
        }
    }
}

CSRedisCore

在NuGet上安装CSRedisCore,然后在appsettings.json文件里面添加Redis相关配置信息:

{
  "RedisServer": {
    "Cache": "192.168.0.3:6379,password=redis,preheat=5,idleTimeout=600,defaultDatabase=13,prefix=Cache"
  },
  "Logging": {
    "IncludeScopes": false,
    "LogLevel": {
      "Default": "Trace",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  }
}

新建一个 IRedisClient 接口

1 public interface IRedisClient
2     {
3         string Get(string key);
4         Task<string> GetAsync(string key);
5         void Set(string key, object t, int expiresSec = 0);
6         Task SetAsync(string key, object t, int expiresSec = 0);
7         T Get<T>(string key) where T : new();
8         Task<T> GetAsync<T>(string key) where T : new();
9     }

实现接口

 1 public class CustomerRedis : IRedisClient
 2     {
 3         public string Get(string key)
 4         {
 5             return RedisHelper.Get(key);
 6         }
 7 
 8         public T Get<T>(string key) where T : new()
 9         {
10             return RedisHelper.Get<T>(key);
11         }
12 
13         public async Task<string> GetAsync(string key)
14         {
15             return await RedisHelper.GetAsync(key);
16         }
17 
18         public async Task<T> GetAsync<T>(string key) where T : new()
19         {
20             return await RedisHelper.GetAsync<T>(key);
21         }
22 
23         public void Set(string key, object t, int expiresSec = 0)
24         {
25             RedisHelper.Set(key, t, expiresSec);
26         }
27 
28         public async Task SetAsync(string key, object t, int expiresSec = 0)
29         {
30             await RedisHelper.SetAsync(key, t, expiresSec);
31         }
32     }

Startup

1 services.AddScoped<IRedisClient, CustomerRedis>();
2 
3             var redisConn = Configuration["Cache:RedisConnection"];
4             //services.Configure<WeChatPayOptions>(Configuration.GetSection("WeChatPay"));
5             var csredis = new CSRedis.CSRedisClient(redisConn);
6             RedisHelper.Initialization(csredis);

调用

 1 private readonly IRedisClient _redisClient;
 2         public ValuesController(IRedisClient redisClient)
 3         {
 4             this._redisClient = redisClient;
 5         }
 6 
 7         [HttpGet("test")]
 8         public async Task<ActionResult> Test()
 9         {
10             await this._redisClient.SetAsync("test", "test",100);
11             var res = await this._redisClient.GetAsync("test");
12             return Ok(res);
13         }

github地址:https://github.com/2881099/csredis

 

posted @ 2020-08-24 11:05  netlock  阅读(2302)  评论(0编辑  收藏  举报