1、问题:我们平时做开发的时候肯定都有用到缓存这个功能,一般写法是在需要的业务代码里读取缓存、判断是否存在、不存在则读取数据库再设置缓存这样一个步骤。但是如果我们有很多地方业务都有用到缓存,我们就需要在每个地方都写关于缓存的代码,这样会造成很多重复代码,同时对业务侵入不利于后续的开发维护

2、一般的解决办法是将缓存的功能提取出来,然后在需要用到缓存的地方调用即可。这样确实减少了很多重复代码,但这样还是会存在整个项目通用的缓存功能侵入业务代码,那我们有什么办法将缓存功能完全提取出来,达到业务代码零侵入呢?

3、既然我们缓存存的是接口的业务数据,那么为何我们不能直接把整个接口缓存起来呢,即将整个接口返回的数据缓存?同时要达到业务零侵入,那我们是不是想到了反射、特性呢?没错,我们使用的就是ActionFilterAttribute,关于ActionFilterAttribute无非就是OnActionExecuting(执行动作方法前触发)、OnActionExecuted(执行动作方法后触发)、OnResultExecuting(在执行操作结果之前触发)、OnResultExecuted(在执行操作结果之后触发)这四个方法,相信很多小伙伴都用到过,这里就不细说了。那我们现在的解决方案是:在OnActionExecuting(执行动作方法前触发)里判断是否存在缓存,如果存在则不去执行接口业务,直接返回数据。还有一个问题,一般接口都会有入参,入参不同输出的数据也不同(比如我有一个分页的接口,传的page参数不同,得到的结果也不同),这个怎么解决呢?我们只需要把接口所有参数拼凑起来,然后MD5加密成一个字符串,将其作为缓存的key,那么即使同一个接口、参数不同也会得到不同的key

4、废话不多说,直接上代码。

public class ApiCache : ActionFilterAttribute
{
/// <summary>
/// Header是否参与缓存验证
/// </summary>
public bool SignHeader = false;
/// <summary>
/// 缓存有效时间(分钟)
/// </summary>
public int CacheMinutes = ;/// <summary>
///
/// </summary>
/// <param name="SignHeader">Header是否参与请求体签名</param>
/// <param name="CacheMinutes">缓存有效时间(分钟)</param>
public ApiCache(bool SignHeader = false, int CacheMinutes = )
{
this.SignHeader = SignHeader;
this.CacheMinutes = CacheMinutes;
} public override void OnActionExecuting(ActionExecutingContext filterContext)
{
//请求体签名
string cacheKey = getKey(filterContext.HttpContext.Request);
//根据签名查询缓存
string data = CsRedisHepler.Get(cacheKey);
if (!string.IsNullOrWhiteSpace(data))
{
//有缓存则设置返回信息
var content = new Microsoft.AspNetCore.Mvc.ContentResult();
content.Content = data;
content.ContentType = "application/json; charset=utf-8";
content.StatusCode = ;
filterContext.HttpContext.Response.Headers.Add("ContentType", "application/json; charset=utf-8");
filterContext.HttpContext.Response.Headers.Add("CacheData", "Redis");
filterContext.Result = content;
}
} public override void OnActionExecuted(ActionExecutedContext filterContext)
{
base.OnActionExecuted(filterContext);
} public override void OnResultExecuting(ResultExecutingContext filterContext)
{
base.OnResultExecuting(filterContext);
} public override void OnResultExecuted(ResultExecutedContext filterContext)
{
if (filterContext.HttpContext.Response.Headers.ContainsKey("CacheData")) return;
//获取缓存key
string cacheKey = getKey(filterContext.HttpContext.Request);
var data = JsonSerializer.Serialize((filterContext.Result as Microsoft.AspNetCore.Mvc.ObjectResult).Value);
//如果缓存null,则设置较短过期时间(此处是防止缓存穿透)
var disData = JsonSerializer.Deserialize<Dictionary<string, object>>(data);
if(disData.ContainsKey("data") && disData["data"]==null)
{
CacheMinutes = ;
}
CsRedisHepler.Set(cacheKey, data, TimeSpan.FromMinutes(CacheMinutes));
}
/// <summary>
/// 请求体MDH签名
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
private string getKey(HttpRequest request)
{
var keyContent = request.Host.Value + request.Path.Value + request.QueryString.Value + request.Method + request.ContentType + request.ContentLength;
try
{
if (request.Method.ToUpper() != "DELETE" && request.Method.ToUpper() != "GET" && request.Form.Count > )
{
foreach (var item in request.Form)
{
keyContent += $"{item.Key}={item.Value.ToString()}";
}
}
}
catch (Exception e)
{ }
if (SignHeader)
{
var hs = request.Headers.Where(a => !(new string[] { "Postman-Token", "User-Agent" }).Contains(a.Key)).ToDictionary(a => a);
foreach (var item in hs)
{
keyContent += $"{item.Key}={item.Value.ToString()}";
}
}
       //md5加密
return CryptographyHelper.MD5Hash(keyContent);
}

这里使用的是redis,也可以选择其他的,代码简单没有做适配,这样我们只需要在用到缓存的接口上加上[ApiCache(CacheMinutes =1)]特性就行啦,关于参数的话也可以根据自己的业务需求来定制。

5、关于缓存的三座大山:缓存穿透、缓存击穿、缓存雪崩,这块网上有很多的资料可以看,这里只做一个简单的介绍跟解决思路。

缓存穿透:访问一个不存在的key时,请求会穿过缓存直接请求数据库。比如现在有个接口是分页的,然后客户端请求接口的时候将pageindex参数给的很大,大到该接口不可能有这么多页的数据时,每次请求都会穿过缓存去查数据库。如果有人故意攻击接口就会给数据库造成巨大压力甚至挂掉。当然,这里我们肯定也要做一些业务参数的校验,比如每页条数不能超过多少之类的,总之不能轻信客户端传过来的参数

解决方案:最简单有效的解决方案是当在数据库也查不到数据的时候,设置一个value为null的缓存值(该值的过期时间要尽量短),这样就可以避免恶意攻击。另外就是使用布隆过滤器。

我们这里使用的解决方案是第一种设置null值,在上述的代码中有注释。不过这里最好接口有一个返回规范,比如每个接口返回固定值:message、code、data这几个字段, 那么我们只需判断data是否为空来设置过期时间。

缓存击穿:某一个访问量极高的key过期,导致所有请求打在数据库上

解决方案:将访问量高德key设置永不过期、使用互斥锁。我们这里使用设置key永不过期就行,具体实现就是加一个是否过期的字段从外部传入,再根据该字段判断是否设置过期时间。同时可以写一个定时任务去更新设置为永不过期的key值。

缓存雪崩:某一时刻多个高访问量的key同时过期

解决方案:在设置过期时间的时候将每个key的过期时间设置分布开来,在上述代码中CacheMinutes字段改成过期时间范围从。。。到。。。,然后key的过期时间从范围中取一个随机值。

当然这里讲到的解决方案也只是个人常用的,也可以使用其他解决方案。

6、最后,已经很久没更新博客了,是我太懒了,只想白p别人的文章。还是很敬佩哪些经常更新博客的大佬,首先文章要有技术点、然后还要考虑怎样将自己对技术点的想法、经验、理解表达出来,真的很不容易。然后就是文章有什么错误点或者可以改进的地方望指正。

.NetCore之接口缓存的更多相关文章

  1. netcore中的缓存介绍

    Cache(缓存)是优化web应用的常用方法,缓存存放在服务端的内存中,被所有用户共享.由于Cache存放在服务器的内存中,所以用户获取缓存资源的速度远比从服务器硬盘中获取快,但是从资源占有的角度考虑 ...

  2. 接口缓存--把接口放在redis数据库中,减少访问量

    针对访问量大,且数据较固定的接口,建议建立接口缓存,建立了缓存之后,不会再直接去访问接口了. 比如下面的轮播图接口,每刷新一下首页都会访问一下轮播图接口,所以我们用接口缓存来处理,减少访问量. 视图模 ...

  3. 在 WPF 客户端实现 AOP 和接口缓存

    随着业务越来越复杂,最近决定把一些频繁查询但是数据不会怎么变更的接口做一下缓存,这种功能一般用 AOP 就能实现了,找了一下客户端又没现成的直接可以用,嗐,就只能自己开发了. 代理模式和AOP 理解代 ...

  4. asp.net mvc,基于aop实现的接口访问统计、接口缓存等

    其实asp.net 上aop现有的框架应该蛮多的,比如静态注入式的PostSharp(新版本好像已经商业化了,旧版本又不支持.net4.0+),或者通过反射的(性能会降低). 本文则是通过mvc其中一 ...

  5. springboot 2.x整合redis,spring aop实现接口缓存

    pox.xml: <dependency> <groupId>org.springframework.boot</groupId> <artifactId&g ...

  6. Nresource服务之接口缓存化

    1. 背景 Nresource服务日均4.5亿流量,考虑到未来流量急增场景,我们打算对大流量接口进行缓存化处理:根据服务管理平台数据统计显示getUsableResoureCount接口调用量很大,接 ...

  7. Swift中对C语言接口缓存的使用以及数组、字符串转为指针类型的方法

    由于Swift编程语言属于上层编程语言,而Swift中由于为了低层的高性能计算接口,所以往往需要C语言中的指针类型,由此,在Swift编程语言刚诞生的时候就有了UnsafePointer与Unsafe ...

  8. NetCore MemoryCache使用

    引用类库 1.Install-Package Microsoft.Extensions.Caching.Memory MemoryCacheOptions 缓存配置 1.ExpirationScanF ...

  9. .NET CORE 中的缓存使用

    Net Framewoke的缓存 1.1 System.Web.Caching System.Web.Caching应该是我们最熟悉的缓存类库了,做ASP.NET开发时用到缓存基本都是使用的这个缓存组 ...

随机推荐

  1. 【算法•日更•第三十一期】KMP算法

    ▎前言 这次要讲的HMP算法KMP算法很简单,是用于处理字符串的,之前一直以为很难,其实也不过如此(说白了就是优化一下暴力). ▎处理的问题 通常处理的问题是这样的:给定两个字符串s1和s2,其中s1 ...

  2. Android PopupWindow显示之后所在的Activity结束的时候出现短暂黑屏问题

    在当前Activity弹出PopuoWindow后,点击取消弹窗,然后结束当前Activity时会出现短暂黑屏现象.这是由于设置背景透明度时候造成的. //设置添加屏幕的背景透明度 public vo ...

  3. 解决MySql Access denied for user 'root'@'192.168.1.119' to databse 的问题

    因为ip未授权,在navicat中执行 grant all privileges on *.* to 'root'@'192.168.1.119' identified by 'root' with ...

  4. 极简 Node.js 入门 - 3.4 文件夹写入

    极简 Node.js 入门系列教程:https://www.yuque.com/sunluyong/node 本文更佳阅读体验:https://www.yuque.com/sunluyong/node ...

  5. Java 8新特性(二):Stream API

    本篇文章继续介绍Java 8的另一个新特性--Stream API.新增的Stream API与InputStream和OutputStream是完全不同的概念,Stream API是对Java中集合 ...

  6. LG P4161 [SCOI2009]游戏/LG P6280 [USACO20OPEN]Exercise G

    Description(P4161) windy学会了一种游戏. 对于1到N这N个数字,都有唯一且不同的1到N的数字与之对应. 最开始windy把数字按顺序1,2,3,……,N写一排在纸上. 然后再在 ...

  7. JMH-大厂是如何使用JMH进行Java代码性能测试的?必须掌握!

    Java 性能测试难题 现在的 JVM 已经越来越为智能,它可以在编译阶段.加载阶段.运行阶段对代码进行优化.比如你写了一段不怎么聪明的代码,到了 JVM 这里,它发现几处可以优化的地方,就顺手帮你优 ...

  8. DNSPod 修改NS 服务器?

    其实我几乎没在国内注册过域名,更没想过用国内的DNS 服务,DNSPod 也是属于听说过名字的地步而已,但是正好在腾讯云注册了一个cn 域名,又觉得对DNSPod 的DNS 服务不是特别满意,所以想把 ...

  9. Spark SQL dropDuplicates

    spark sql 数据去重 在对spark sql 中的dataframe数据表去除重复数据的时候可以使用dropDuplicates()方法 dropDuplicates()有4个重载方法 第一个 ...

  10. linux系统工程师修改打开文件数限制代码教程。服务器运维技术

    提示linux文件打开错误,修改linux打开文件数参数. /etc/pam.d/login 添加 session required /lib/security/pam_limits.so 注意看这个 ...