一、本地缓存

  • 设计思路
    查询数据时先查看本地缓存中是否有数据,如果有数据直接返回,如果没有数据,到数据库查询后添加到本地缓存,并将数据返回。
  • 优缺点
  • 缺点
    Memory是服务器内存的缓存,如果并发量大并查询的数据不一致,会造成内存非常大,同时会造成GC不断的回收内存,由于Memory内部使用的是静态变量,造成内存无法回收,GC每回收一次,就会耗费一次CPU资源,如果GC回收的频率比较大,大么耗费的CPU资源就较大。
  • 解决方案:1.设置缓存时间。2.设置缓存大小。
  • 优点
    数据读写速度时间缩短,性能提升。
  • 使用
  • 安装Nuget包

Microsoft.Extensions.Caching.Memory

  • Startup.cs注册
//ConfigureServices方法中注册缓存服务
Service.AddMemoryCache(options=>{
options.SizeLimit = 1024*1024*100; //设置缓存大小
});
  • 使用方法
//在构造方法中注入
private readonly IMemoryCache memoryCache;
构造函数 (IMemoryCache _memoryCache)
{
memoryCache =_memoryCache;
}
//测试对象
Person per = new Person();
//查询缓存方法
//memoryCache.Get<Person>(key);
//为了放防止多线程并发
bool flag = memoryCache.TryGetValue<缓存对象>(key,out per);
//true:有数据 false:无数据
//限制缓存大小
var cacheEntryOptions = new MemoryCacheEntryOptions().
//设置单个缓存大下
SetSize(1024).
//设置过期时间 自动失效
SetSlidingExpiration(TimeSpan.FromSeconds(3));
//添加缓存
memoryCache.Set<Person>(key,value,cacheEntryOptions);

二、分布式缓存

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

  • 原理
    Redis数据库中的数据时存放在内存中,并非磁盘中,不需要把每次查询进行IO操作。把使用的数据查询加载的内存中,在内存中操作,提升查询效率。
  • 使用场景
    1.任何可丢失数据。2.不经常变动的数据。
  • 使用方式
  • 在appsetting.json添加Redis配置

"ConnectionStrings: RedisCaching节点配置信息

{
  "ConnectionStrings": {
    "ConnectionString": "Data Source=127.0.0.1;Initial Catalog=db;User ID=uid;Password=123456;Pooling=True;Max Pool Size=512;Connect Timeout=500;",
    "JwtSetting": {
      "Issuer": "jwtIssuer", //颁发者
      "Audience": "jwtAudience", //可以给哪些客户端使用
      "SecretKey": "chuangqianmingyueguang" //加密的Key
    },
    "RedisCaching": {
      "Enabled": true,
      "ConnectionString": "127.0.0.1:6379"
    }
  }
}
  • 添加SerializeHelper.cs 对象序列化操作
public class SerializeHelper
    {
        /// <summary>
        /// 序列化
        /// </summary>
        /// <param name="item"></param>
        /// <returns></returns>
        public static byte[] Serialize(object item)
        {
            var jsonString = JsonConvert.SerializeObject(item);

            return Encoding.UTF8.GetBytes(jsonString);
        }
        /// <summary>
        /// 反序列化
        /// </summary>
        /// <typeparam name="TEntity"></typeparam>
        /// <param name="value"></param>
        /// <returns></returns>
        public static TEntity Deserialize<TEntity>(byte[] value)
        {
            if (value == null)
            {
                return default(TEntity);
            }
            var jsonString = Encoding.UTF8.GetString(value);
            return JsonConvert.DeserializeObject<TEntity>(jsonString);
        }
    }
  • 定义接口和实现类

新建IRedisCacheManager接口和RedisCacheManager类,并引用Nuget包StackExchange.Redis

public class RedisCacheManager : IRedisCacheManager
    {
        private readonly string redisConnenctionString;
        public volatile ConnectionMultiplexer redisConnection;
        private readonly object redisConnectionLock = new object();
        public RedisCacheManager()
        {
            string redisConfiguration = ConfigHelper.GetSectionValue("ConnectionStrings:RedisCaching:ConnectionString");//获取连接字符串

            if (string.IsNullOrWhiteSpace(redisConfiguration))
            {
                throw new ArgumentException("redis config is empty", nameof(redisConfiguration));
            }
            this.redisConnenctionString = redisConfiguration;
            this.redisConnection = GetRedisConnection();
        }

        /// <summary>
        /// 核心代码,获取连接实例
        /// 通过双if 夹lock的方式,实现单例模式
        /// </summary>
        /// <returns></returns>
        private ConnectionMultiplexer GetRedisConnection()
        {
            //如果已经连接实例,直接返回
            if (this.redisConnection != null && this.redisConnection.IsConnected)
            {
                return this.redisConnection;
            }
            //加锁,防止异步编程中,出现单例无效的问题
            lock (redisConnectionLock)
            {
                if (this.redisConnection != null)
                {
                    //释放redis连接
                    this.redisConnection.Dispose();
                }
                try
                {
                    this.redisConnection = ConnectionMultiplexer.Connect(redisConnenctionString);
                }
                catch (Exception)
                {

                    throw new Exception("Redis服务未启用,请开启该服务");
                }
            }
            return this.redisConnection;
        }

        public void Clear()
        {
            foreach (var endPoint in this.GetRedisConnection().GetEndPoints())
            {
                var server = this.GetRedisConnection().GetServer(endPoint);
                foreach (var key in server.Keys())
                {
                    redisConnection.GetDatabase().KeyDelete(key);
                }
            }
        }

        public bool Get(string key)
        {
            return redisConnection.GetDatabase().KeyExists(key);
        }

        public string GetValue(string key)
        {
            return redisConnection.GetDatabase().StringGet(key);
        }

        public TEntity Get<TEntity>(string key)
        {
            var value = redisConnection.GetDatabase().StringGet(key);
            if (value.HasValue)
            {
                //需要用的反序列化,将Redis存储的Byte[],进行反序列化
                return SerializeHelper.Deserialize<TEntity>(value);
            }
            else
            {
                return default(TEntity);
            }
        }

        public void Remove(string key)
        {
            redisConnection.GetDatabase().KeyDelete(key);
        }

        public void Set(string key, object value, TimeSpan cacheTime)
        {
            if (value != null)
            {
                //序列化,将object值生成RedisValue
                redisConnection.GetDatabase().StringSet(key, SerializeHelper.Serialize(value), cacheTime);
            }
        }

        public bool SetValue(string key, byte[] value)
        {
            return redisConnection.GetDatabase().StringSet(key, value, TimeSpan.FromSeconds(120));
        }

    }
  • 将Redis服务注入到容器中

在ConfigureServices中 进行注入:

//注册Redis
services.AddSingleton<IRedisCacheManager, RedisCacheManager>();
  • 控制器中使用
/// <summary>
/// 测试Redis
/// </summary>
/// <returns></returns>
[HttpGet]
public async Task<IActionResult> Redis(int id)
{

    var key = $"Redis{id}";
    UserNew user = new UserNew();
    if (_redisCacheManager.Get<object>(key) != null)
    {
        user = _redisCacheManager.Get<UserNew>(key);
    }
    else
    {
        user = new UserNew
        {
            UserId = id,
            UserName = "bingle",
            Age = 18
        };
        _redisCacheManager.Set(key, user, TimeSpan.FromHours(2));//缓存2小时
    }

    return Ok(user);
}

三、响应缓存

  • 原理
    当客户端第一次请求服务器,服务器响应后,服务器会往响应头里写入两个参数 :【1、是否开启缓存存储数据。2、校验】,并存储到客户端,客户端会将数据存储到缓存仓库中;当客户端第二次请求到服务器,会将etag传到服务器进行校验,如果两个etag是相等的,服务端会返给客户端304,客户端会从缓存仓库中获取数据。
  • 场景:
    不可变的数据使用。
  • 缺陷
    会浪费大量的客户端和服务器进行交互。
  • 协商缓存:
  • 安装Nuget包

Marvin.Cache.Headers

  • 在Startup.cs中注册
//ConfigureServices方法中注册
Service.AddHttpCacheHeaders((options)=>{options.MaxAge = ....;//设置过期时间 默认60s
options.CacheLocation = ....;//public 公共的 private 私有的只能当前客户端使用
options.NoStore= ...;// 设置响应头信息 不走本地缓存
options.NoTransform= ....;//设置请求头信息
},
(options1)=>{});
//Configure方法中启动并存储校验信息
app.UseHttpCacheHeaders();
  • 控制器中添加代码
HttpContext.Response.Headers.Add("cache-control","max-age=60,public"); //是否开启缓存储数据 设置缓存时间
HttpContext.Response.Headers.Add("etag",Value);//校验
HttpContext.Response.Headers.Add("last-modified","Mon,24 Dec 2022 09:49:49 GMT");
  • 强制缓存:
  • 安装Nuget包:

Marvin.Cache.Headers

  • 在Startup.cs中注册
//ConfigureServices方法中注册
Service.AddHttpCacheHeaders((options)=>{
options.MustRevalidate = true; //全局的方式 不建议使用
});
//Configure方法中启动并存储校验信息
app.UseHttpCacheHeaders();
  • 控制器中添加代码
HttpContext.Response.Headers.Add("cache-control","max-age=60,public,must-revalidate");
  • 针对某个控制器使用
//在控制器方法上添加特性
[HttpCacheExpiration(CacheLocation = CacheLocation.Public,MaxAge=60)] 设置NoStore = true 不走缓存
[HttpCacheValidation(MustRevalidate = true)]
  • 使用场景
    1.字典数据
    2.静态资源,如图片,视频,文本等。
    3.js或者css文件

四、数据压缩

  • 在Startup类ConfigureServices方法中注册
//响应数据压缩
services.AddResponseCompression();
  • 在Startup类Configure方法中开启服务
//必须写在中间件的开头
app.UseResponseCompression();
  • 数据压缩的目的
    传输数据的时候,减少数据传输的带宽,提升性能。
  • 场景
    只要涉及到数据传输,就可以进行压缩

技术的发展日新月异,随着时间推移,无法保证本博客所有内容的正确性。如有误导,请大家见谅,欢迎评论区指正!

开源库地址,欢迎Star点亮:

GitHub:https://github.com/ITMingliang

Gitee:   https://gitee.com/mingliang_it

GitLab: https://gitlab.com/ITMingliang


建群声明: 本着技术在于分享,方便大家交流学习的初心,特此建立【编程内功修炼交流群】,为大家答疑解惑。热烈欢迎各位爱交流学习的程序员进群,也希望进群的大佬能不吝分享自己遇到的技术问题和学习心得!进群方式:扫码关注公众号,后台回复【进群】。

WebAPI性能优化小结_数据