zoukankan      html  css  js  c++  java
  • NETCORE

    ETCORE - Redis的使用2

    缓存主要针对的是不经常发生改变的并且访问量很大的数据,DB数据库可以理解为只作为数据固化的或者只用来读取经常发生改变的数据,图中没有画SET的操作,就是想特意说明一下,缓存的存在可以作为一个临时的数据库,我们可以通过定时的任务的方式去同步缓存和数据库中的数据,这样做的好处是可以转移数据库的压力到缓存中。

      

    缓存的出现解决了数据库压力的问题,但是当以下情况发生的时候,缓存就不在起到作用了,缓存穿透、缓存击穿、缓存雪崩这三种情况

    1. 缓存穿透:

    我们的程序中用缓存的时候一般采取的是先去缓存中查询我们想要的缓存数据,如果缓存中不存在我们想要的数据的话,缓存就失去了做用(缓存失效)我们就是需要伸手向DB库去要数据,这个时候这种动作过多数据库就崩溃了,这种情况需要我们去预防了。比如说:我们向缓存获取一个用户信息,但是故意去输入一个缓存中不存在的用户信息,这样就避过了缓存,把压力重新转移到数据上面了。对于这种问题我们可以采取,把第一次访问的数据进行缓存,因为缓存查不到用户信息,数据库也查询不到用户信息,这个时候避免重复的访问我们把这个请求缓存起来,把压力重新转向缓存中,有人会有疑问了,当访问的参数有上万个都是不重复的参数并且都是可以躲避缓存的怎么办,我们同样把数据存起来设置一个较短过期时间清理缓存。


    2. 缓存击穿:

    事情是这样的,对于一些设置了过期时间的缓存KEY,在过期的时候,程序被高并发的访问了(缓存失效),这个时候使用互斥锁来解决问题,

      互斥锁原理:通俗的描述就是,一万个用户访问了,但是只有一个用户可以拿到访问数据库的权限,当这个用户拿到这个权限之后重新创建缓存,这个时候剩下的访问者因为没有拿到权限,就原地等待着去访问缓存。

      永不过期:有人就会想了,我不设置过期时间不就行了吗?可以,但是这样做也是有缺点的,我们需要定期的取更新缓存,这个时候缓存中的数据比较延迟。

    3. 缓存雪崩:

    是指多种缓存设置了同一时间过期,这个时候大批量的数据访问来了,(缓存失效)数据库DB的压力又上来了。解决方法在设置过期时间的时候在过期时间的基础上增加一个随机数尽可能的保证缓存不会大面积的同事失效。

    一、安装Redis 

    windows:https://www.cnblogs.com/1285026182YUAN/p/10812826.html

    docker:https://www.cnblogs.com/1285026182YUAN/p/12742410.html

    二、创建项目

    项目名称:NETCORE.Redis

    netcore版本:3.1

    1. 引用nuget包:

    Microsoft.Extensions.Caching.StackExchangeRedis
    Newtonsoft.Json

    Microsoft.Extensions.Caching.Redis会被逐渐淘汰,不再用了。

     

     2. appsettings.json配置文件

    {
      "Logging": {
        "LogLevel": {
          "Default": "Information",
          "Microsoft": "Warning",
          "Microsoft.Hosting.Lifetime": "Information"
        }
      },
      "AllowedHosts": "*",
      "RedisConnections": {
        "IP": "10.5.30.239:6380",
        "Password": "123456",
        "Database": 10
      }
    }

     3. Startup.cs文件

     在ConfigureServices方法中先注册Redis服务

            public void ConfigureServices(IServiceCollection services)
            {
                services.AddStackExchangeRedisCache(options =>
                { 
                    options.InstanceName = "DemoInstance";
    
                    options.ConfigurationOptions = new StackExchange.Redis.ConfigurationOptions()
                    {
                        Password = Configuration.GetSection("RedisConnections")["Password"],
                        DefaultDatabase = Convert.ToInt32(Configuration.GetSection("RedisConnections")["Database"]),
                        ConnectTimeout = 5000,//设置建立连接到Redis服务器的超时时间为5000毫秒
                        SyncTimeout = 5000,//设置对Redis服务器进行同步操作的超时时间为5000毫秒
                      //ResponseTimeout = 5000//设置对Redis服务器进行操作的响应超时时间为5000毫秒,//注意,ResponseTimeout属性在Microsoft.Extensions.Caching.StackExchangeRedis中已经被弃用
                        Ssl = false,//设置Redis启用SSL安全加密传输数据。
                        SslProtocols = System.Security.Authentication.SslProtocols.Tls
                    };
    
                    options.ConfigurationOptions.EndPoints.Add(Configuration.GetSection("RedisConnections")["IP"]);
                });
    
                var provider = services.BuildServiceProvider();
    
                // 注入RedisCache
                var cache = provider.GetService<IDistributedCache>();
                services.AddSingleton(new RedisCache(cache));
    
                services.AddControllers();
            }

    4. 封装一个RedisCache类

    在项目中引用:using Microsoft.Extensions.Caching.Distributed; 使用IDistributedCache

    IDistributedCache接口包含同步和异步方法。 接口允许在分布式缓存实现中添加、检索和删除项。 IDistributedCache接口包含以下方法:
    Get、 GetAsync
    采用字符串键并以byte[]形式检索缓存项(如果在缓存中找到)。如果传入Get方法的键在Redis中不存在,那么Get方法会返回null。
    Set、SetAsync
    使用字符串键向缓存添加或更改项(byte[]形式)。
    Refresh、RefreshAsync
    根据键刷新缓存中的项,并重置其滑动过期超时值(如果有)。如果传入Refresh方法的键在Redis中不存在,Refresh方法不会报错,只是什么都不会发生而已,但是如果传入Refresh方法的参数为null,则会抛出异常。
    Remove、RemoveAsync
    根据键删除缓存项。如果传入Remove方法的键在Redis中不存在,Remove方法不会报错,只是什么都不会发生而已,但是如果传入Remove方法的参数为null,则会抛出异常。

     其中用到了Json.NET Nuget包,来做Json格式的序列化和反序列化

    如上所述,由于IDistributedCache接口的Set和Get方法,是通过byte[]字节数组来向Redis存取数据的,所以从某种意义上来说不是很方便,下面封装了一个RedisCache类,可以向Redis中存取任何类型的数据。

    using Microsoft.Extensions.Caching.Distributed;
    using Newtonsoft.Json;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Text.Json.Serialization;
    using System.Threading.Tasks;
    
    namespace NETCORE.Redis
    {
        /// <summary>
        /// RedisCache缓存操作类
        /// </summary>
        public class RedisCache
        {
            protected IDistributedCache cache;
    
            /// <summary>
            /// 通过IDistributedCache来构造RedisCache缓存操作类
            /// </summary>
            /// <param name="cache">IDistributedCache对象</param>
            public RedisCache(IDistributedCache cache)
            {
                this.cache = cache;
            }
    
    
            /// <summary>
            /// 添加或更改Redis的键值,并设置缓存的过期策略
            /// </summary>
            /// <param name="key">缓存键</param>
            /// <param name="value">缓存值</param>
            /// <param name="distributedCacheEntryOptions">
            /// 设置Redis缓存的过期策略,
            /// 可以用其设置缓存的绝对过期时间
            /// (AbsoluteExpiration或AbsoluteExpirationRelativeToNow),
            /// 也可以设置缓存的滑动过期时间(SlidingExpiration)
            /// </param>
            public void Set(string key, object value, DistributedCacheEntryOptions distributedCacheEntryOptions)
            {
                //通过Json.NET序列化缓存对象为Json字符串
                //调用JsonConvert.SerializeObject方法时,设置ReferenceLoopHandling属性为ReferenceLoopHandling.Ignore,来避免Json.NET序列化对象时,因为对象的循环引用而抛出异常
                //设置TypeNameHandling属性为TypeNameHandling.All,这样Json.NET序列化对象后的Json字符串中,会包含序列化的类型,这样可以保证Json.NET在反序列化对象时,去读取Json字符串中的序列化类型,从而得到和序列化时相同的对象类型
                var stringObject = JsonConvert.SerializeObject(value, new JsonSerializerSettings()
                {
                    ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
                    TypeNameHandling = TypeNameHandling.All
                }); ;
    
                var bytesObject = Encoding.UTF8.GetBytes(stringObject);//将Json字符串通过UTF-8编码,序列化为字节数组
    
                cache.Set(key, bytesObject, distributedCacheEntryOptions);//将字节数组存入Redis
    
                Refresh(key);//刷新Redis
            }
    
    
            /// <summary>
            /// 查询键值是否在Redis中存在
            /// </summary>
            /// <param name="key">缓存键</param>
            /// <returns>true:存在,false:不存在</returns>
            public bool Exist(string key)
            {
                var bytesObject = cache.Get(key);//从Redis中获取键值key的字节数组,如果没获取到,那么会返回null
    
                var flag = bytesObject != null;
    
                return flag;
            }
    
    
            /// <summary>
            /// 从Redis中获取键值
            /// </summary>
            /// <typeparam name="T">缓存的类型</typeparam>
            /// <param name="key">缓存键</param>
            /// <param name="isExisted">是否获取到键值,true:获取到了,false:键值不存在</param>
            /// <returns>缓存的对象</returns>
            public T Get<T>(string key, out bool isExisted)
            {
                var bytesObject = cache.Get(key);//从Redis中获取键值key的字节数组,如果没获取到,那么会返回null
    
                if (bytesObject == null)
                {
                    isExisted = false;
                    return default(T);
                }
    
                var stringObject = Encoding.UTF8.GetString(bytesObject);//通过UTF-8编码,将字节数组反序列化为Json字符串
    
                isExisted = true;
    
    
                //通过Json.NET反序列化Json字符串为对象
                //调用JsonConvert.DeserializeObject方法时,也设置TypeNameHandling属性为TypeNameHandling.All,这样可以保证Json.NET在反序列化对象时,去读取Json字符串中的序列化类型,从而得到和序列化时相同的对象类型
                var resValue = JsonConvert.DeserializeObject<T>(stringObject, new JsonSerializerSettings()
                {
                    TypeNameHandling = TypeNameHandling.All
                });
    
                return resValue;
            }
    
    
            /// <summary>
            /// 从Redis中删除键值,如果键值在Redis中不存在,该方法不会报错,只是什么都不会发生
            /// </summary>
            /// <param name="key">缓存键</param>
            public void Remove(string key)
            {
                cache.Remove(key);//如果键值在Redis中不存在,IDistributedCache.Remove方法不会报错,但是如果传入的参数key为null,则会抛出异常
            }
    
            /// <summary>
            /// 从Redis中刷新键值
            /// </summary>
            /// <param name="key">缓存键</param>
            public void Refresh(string key)
            {
                cache.Refresh(key);
            }
        }
    }
    View Code

    5.  创建TestController.cs WebAPI控制器

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading.Tasks;
    using Microsoft.AspNetCore.Http;
    using Microsoft.AspNetCore.Mvc;
    using Microsoft.Extensions.Caching.Distributed;
    
    namespace NETCORE.Redis.Controllers
    {
        [Route("api/[controller]")]
        [ApiController]
        public class TestController : ControllerBase
        {
            protected RedisCache redisCache;
    
            public TestController(RedisCache redisCache)
            {
                this.redisCache = redisCache;
            }
    
    
            [HttpGet]
            [Route("Get1")]
            public IActionResult Get1()
            {
                bool isExisted;
                isExisted = redisCache.Exist("abc");//查询键值"abc"是否存在
                redisCache.Remove("abc");//删除不存在的键值"abc",不会报错
    
                string key = "Key01";//定义缓存键"Key01"
                string value = "This is a demo key !";//定义缓存值
    
                redisCache.Set(key, value, new DistributedCacheEntryOptions()
                {
                    AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(10)
                });//设置键值"Key01"到Redis,使用绝对过期时间,AbsoluteExpirationRelativeToNow设置为当前系统时间10分钟后过期
    
                //也可以通过AbsoluteExpiration属性来设置绝对过期时间为一个具体的DateTimeOffset时间点
                //redisCache.Set(key, value, new DistributedCacheEntryOptions()
                //{
                //    AbsoluteExpiration = DateTimeOffset.Now.AddMinutes(10)
                //});//设置键值"Key01"到Redis,使用绝对过期时间,AbsoluteExpiration设置为当前系统时间10分钟后过期
    
                var getVaue = redisCache.Get<string>(key, out isExisted);//从Redis获取键值"Key01",可以看到getVaue的值为"This is a demo key !"
    
                value = "This is a demo key again !";//更改缓存值
    
                redisCache.Set(key, value, new DistributedCacheEntryOptions()
                {
                    SlidingExpiration = TimeSpan.FromMinutes(10)
                });//将更改后的键值"Key01"再次缓存到Redis,这次使用滑动过期时间,SlidingExpiration设置为10分钟
    
                getVaue = redisCache.Get<string>(key, out isExisted);//再次从Redis获取键值"Key01",可以看到getVaue的值为"This is a demo key again !"
    
                //下面我们来演示如何将键值"Key01"在Redis中设置为永不过期
                redisCache.Remove(key);//首先要从Redis中删除键值"Key01",因为.NET Core无法将一个有绝对过期时间或滑动过期时间的键值在Redis中设置为永不过期,我们只能将键值"Key01"先从Redis中删除,然后重新添加键值"Key01"到Redis
    
                redisCache.Set(key, value, new DistributedCacheEntryOptions()
                {
                    AbsoluteExpiration = null,
                    AbsoluteExpirationRelativeToNow = null,
                    SlidingExpiration = null
                });//在添加键值"Key01"到Redis时,只要将DistributedCacheEntryOptions类的AbsoluteExpiration、AbsoluteExpirationRelativeToNow、SlidingExpiration三个属性都设置为null,那么键值"Key01"在Redis中就没有过期时间,也就是永不过期。
                   //这时可以在redis-cli中,使用指令:"ttl <键名称>",查看到键值"Key01"的剩余时间为-1,表示没有过期时间,永不过期。
    
                redisCache.Set(key, value, new DistributedCacheEntryOptions()
                {
                    AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(30)
                });//虽然.NET Core无法将Redis中有绝对过期时间或滑动过期时间的键值设置为永不过期,但是却可以将一个永不过期的键值设置为有绝对过期时间或滑动过期时间,取消永不过期,只需调用Set方法,将DistributedCacheEntryOptions类的AbsoluteExpiration、AbsoluteExpirationRelativeToNow、SlidingExpiration三个属性之一赋值即可。
    
                redisCache.Remove(key);//从Redis中删除键值"Key01"
    
    
    
    
                return Ok("okk");
            }
        }
    }
    View Code

      

    redis redis-cli 操作指令

    默认选择 db库是 0
    redis-cli -p 6379
     
    查看当前所在“db库”所有的缓存key
    redis 127.0.0.1:6379> keys *
     
    选择 db库
    redis 127.0.0.1:6379> select 8
     
    清除所有的缓存key
    redis 127.0.0.1:6379> FLUSHALL
     
    清除当前“db库”所有的缓存key
    redis 127.0.0.1:6379[8]> FLUSHDB
     
    设置缓存值
    redis 127.0.0.1:6379> set keyname keyvalue
     
    获取缓存值
    redis 127.0.0.1:6379> get keyname
     
    删除缓存值:返回删除数量(0代表没删除)
    redis 127.0.0.1:6379> del keyname 
     

     
    项目:NETCORE.Redis
    附代码:https://gitee.com/wuxincaicai/NETCORE.git

    引用:https://www.cnblogs.com/OpenCoder/p/10287743.html

  • 相关阅读:
    【PC Basic】CPU、核、多线程的那些事儿
    为什么要使用 do while(0)?
    DPDK CAS(compare and set)操作
    编程中Foo,Bar 到底是什么意思
    如何用Python进行ARP攻击?
    有哪些有趣的化学方程式?
    1636. 按照频率将数组升序排序
    1046. 最后一块石头的重量
    1122. 数组的相对排序
    459. 重复的子字符串
  • 原文地址:https://www.cnblogs.com/1285026182YUAN/p/13099146.html
Copyright © 2011-2022 走看看