7.4缓存

缓存机制与最佳实践

| ------------------------------------------------------------ | ------------------------------------------------------------ |
| bool TryGetValue(object key, out object value) | 尝试获取键为key的缓存值,如果有,则返回true否则返回false |
| void Remove(object key) | 删除键为key的缓存值 |
| T Set<T>(object key,T value) | 设置缓存键key的缓存值为value |
| T GetOrCreate<T>(object key,Func<ICacheEntry,T> factory) | 获取键为key的缓存值,如果缓存中没有,则调用factory指向的函数从数据源获取数据,创建缓存并返回值 |
| Task<T> GetOrCreateAsync<T>(object key,Func<ICacheEntry,Task<T>> factory) | 异步的GetOrCreate方法,ICacheEntry可以对缓存项进行详细的设置,比如缓存项被清除的回调、缓存项的优先级 |

[Route("[controller]/[action]")]
[ApiController]
public class Test1Controller : ControllerBase
{
    private readonly ILogger<Test1Controller> logger;
    private readonly MyDbContext dbCtx;
    private readonly IMemoryCache memCache;//使用依赖注入的形式使用IMemoryCache
    public Test1Controller(MyDbContext dbCtx, IMemoryCache memCache, ILogger<Test1Controller> logger)
    {
        this.dbCtx = dbCtx;
        this.memCache = memCache;
        this.logger = logger;
    }
    [HttpGet]
    public async Task<Book[]> GetBooks()
    {
        logger.LogInformation("开始执行GetBooks");
        var items = await memCache.GetOrCreateAsync("AllBooks", async (e) =>
        {
            logger.LogInformation("从数据库中读取数据");
            return await dbCtx.Books.ToArrayAsync();
        });
        logger.LogInformation("把数据返回给调用者");
        return items;
    }
}
过期策略
  • 绝对过期时间

自设置缓存之后的指定时间后,缓存被删除

  • 滑动过期时间

自设置缓存之后的指定时间后,如果对缓存数据没有访问,则删除,如果有访问,则缓存项会以最后一次的时间为准自动续期

//绝对过期时间
logger.LogInformation("开始执行Demo1:" + DateTime.Now);
var items = await memCache.GetOrCreateAsync("AllBooks", async (e) => {
    e.AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(10);//设置绝对过期时间
    logger.LogInformation("从数据库中读取数据");
    return await dbCtx.Books.ToArrayAsync();
});
logger.LogInformation("Demo1执行结束");

//滑动过期时间
logger.LogInformation("开始执行Demo2:" + DateTime.Now);
var items = await memCache.GetOrCreateAsync("AllBooks2", async (e) => {
    e.SlidingExpiration = TimeSpan.FromSeconds(10);//活动过期时间
    logger.LogInformation("Demo2从数据库中读取数据");
    return await dbCtx.Books.ToArrayAsync();
});
logger.LogInformation("Demo2执行结束");
  • 混合使用过期策略

一般设置绝对过期时间比滑动时间长,绝对时间到期后,无论滑动时间有没有到期,都会删除缓存

//混合使用过期时间策略
logger.LogInformation("开始执行Demo3:" + DateTime.Now);
var items = await memCache.GetOrCreateAsync("AllBooks3", async (e) => {
    e.SlidingExpiration = TimeSpan.FromSeconds(10);//滑动时间
    e.AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(30);//绝对时间
    logger.LogInformation("Demo3从数据库中读取数据");
    return await dbCtx.Books.ToArrayAsync();
});
logger.LogInformation("Demo3执行结束");

混合使用过期策略可以实现不经常被访问的数据不会长时间占内存,而频繁被访问的数据会避免数据不一致的问题

所有的缓存都会出现数据不一致的情况,对应不允许数据不一致的情况,可以直接不用缓存。

缓存穿透

使用IMemoryCache中的Get方法,会根据key来查找缓存项,如果找不到则返回Null

string cacheKey = "Book" + id;//缓存键
Book? b = memCache.Get<Book?>(cacheKey);
if (b == null)//如果缓存中没有数据 如果有恶意者使用不存在的某个Id大量访问,则会不断的查询数据库,导致服务器崩溃,这叫做缓存穿透
{
    //查询数据库,然后写入缓存
    b = await dbCtx.Books.FindAsync(id);
    memCache.Set(cacheKey, b);
}

缓存穿透的问题是由于将查不到的数据用Null来表示,如果把“查不到”也当数据放到缓存中,即{key:不存在的某个Id,value:null}。日常开发中使用GetOrCreateAsync方法就可以避免缓存穿透,它将null也作为了合法的缓存值。

logger.LogInformation("开始执行Demo5");
string cacheKey = "Book" + id;
var book = await memCache.GetOrCreateAsync(cacheKey, async (e) => {
    var b = await dbCtx.Books.FindAsync(id);
    logger.LogInformation("数据库查询:{0}", b == null ? "为空" : "不为空");
    return b;
});
logger.LogInformation("Demo5执行结束:{0}", book == null ? "为空" : "不为空");
return book;

缓存雪崩

一般会是在网站启动的时候将大量的数据放到缓存以提高响应速度,如果这些缓存设定了相同的过期时间,则会同时过期,如果有访问则会导致大量的数据库访问,这样会同时的访问会将数据库服务器压垮。解决这个问题的办法是在基础过期的时间上增加一个随机的过期时间,这样就不会集中到同一时间了。

注意:设置缓存的时候,IQueryable、IEnumerable等类型可能存在延迟加载,当取出这种类型的变量时去执行时,如果他们所需要的对象已经被释放则会执行失败。因此最好将这两种对象转换为数组或者List类型再放到缓存中。

分布式缓存

在分布式系统中,将缓存数据放到专门的缓存服务器中,所有的Web都通过缓存服务器来进行写入和读取。

.net core中使用IDistributedCache接口来进行操作,分布式缓存中提供了DistributedCacheEntryOptions来配置过期时间。不同类型的缓存服务器支持的缓存键和缓存值不相同,所以IDistributedCache统一将string作为key的类型,将byte[]作为值类型。

推荐使用Redis数据库作为缓存服务器,微软也提供了Redis作为缓存服务器的Nuget包Microsoft.Extensions.Caching.StackExchangeRedis。

  1. 在Program.cs的builder.Build之前添加代码进行注册Redis缓存
builder.Services.AddStackExchangeRedisCache(options =>
{
    options.Configuration = "localhost";//redis服务器的连接配置
    options.InstanceName = "yzk_";//其他程序也许也在使用redis服务器,为避免冲突,增加yzk_前缀
});
  1. 读写redis中的缓存数据
public class Test1Controller : ControllerBase
{
	private readonly IDistributedCache distCache;
    public Test1Controller(IDistributedCache distCache)
    {
        this.distCache = distCache;
    }
    [HttpGet]
	public string Now()
	{
		string s = distCache.GetString("Now");
		if (s == null)
		{
			s = DateTime.Now.ToString();
			var opt = new DistributedCacheEntryOptions();
			opt.AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(30);
			distCache.SetString("Now", s, opt);//将s存放在yzh_Now键对应的值中
		}
		return s;
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值