• 欢迎光临~

WebAPI性能优化小结

开发技术 开发技术 2022-07-22 次浏览

一、本地缓存

  • 设计思路

    查询数据时先查看本地缓存中是否有数据,如果有数据直接返回,如果没有数据,到数据库查询后添加到本地缓存,并将数据返回。

  • 优缺点

    • 缺点

      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();
    
  • 数据压缩的目的

    传输数据的时候,减少数据传输的带宽,提升性能。

  • 场景

    只要涉及到数据传输,就可以进行压缩

程序员灯塔
转载请注明原文链接:WebAPI性能优化小结
喜欢 (0)