目录
缓存的基本概念
缓存原理
缓存设计
分布式缓存 Memcache 与 Redis 的比较
缓存穿透, 缓存击穿, 缓存雪崩解决方案
数据一致性
使用内置 MemoryCache
使用分布式缓存 Redis
使用 Stackexchange.Redis 自己封装一个 RedisHelper 类
参考
缓存的基本概念
缓存是分布式系统中的重要组件, 主要解决高并发, 大数据场景下, 热点数据访问的性能问题. 提供高性能的数据快速访问.
缓存原理
将数据写入到读取速度更快的存储设备;
将数据缓存到离应用最近的位置;
将数据缓存到离用户最近的位置.
缓存设计
缓存内容 热点数据, 静态资源
缓存位置 CDN, 反向代理, 分布式缓存服务器, 本机 (内存, 硬盘)
反向代理: 动静分离, 只缓存用户请求的静态资源;
分布式缓存: 缓存数据库中的热点数据;
本地缓存: 缓存应用字典等常用数据.
过期策略 固定时间, 相对时间
同步机制 实时写入, 异步刷新
分布式缓存 Memcache 与 Redis 的比较
数据结构: Memcache 只支持 key value 存储方式, Redis 支持更多的数据类型, 比如 Key value,hash,list,set,zset;
多线程: Memcache 支持多线程, Redis 支持单线程; CPU 利用方面 Memcache 优于 Redis;
持久化: Memcache 不支持持久化, Redis 支持持久化 (快照和 AOF 日志两种持久化方式);
内存利用率: Memcache 高, Redis 低 (采用压缩的情况下比 Memcache 高). 使用简单的 key-value 存储的话, Memcached 的内存利用率更高, 而如果 Redis 采用 hash 结构来做 key-value 存储, 由于其组合式的压缩, 其内存利用率会高于 Memcache.
过期策略: Memcache 过期后, 不删除缓存, 会导致下次取数据数据的问题, Redis 有专门线程, 清除缓存数据;
缓存穿透, 缓存击穿, 缓存雪崩解决方案
缓存穿透
缓存穿透是指查询一个一定不存在的数据. 由于会频繁的请求数据库, 对数据库造成访问压力.
解决方法:
对结果为空的数据也进行缓存, 不过设置它的过期时间会很短, 最长不超过五分钟.
一定不存在的 key, 采用布隆过滤器, 建立一个大的 Bitmap 中, 查询时通过该 bitmap 过滤.
缓存雪崩
缓存雪崩是指在我们设置缓存时采用了相同的过期时间, 导致缓存在某一时刻同时失效, 请求全部转发到 DB,DB 瞬时压力过重雪崩.
解决方法:
通过加锁或者队列来控制读数据库写缓存的线程数量. 比如对某个 key 只允许一个线程查询数据和写缓存, 其他线程等待.
分散缓存失效时间, 比如在设置过期时间的时候增加一个随机数尽可能的保证缓存不会大面积的同时失效.
缓存击穿
缓存击穿是指对于一些设置了过期时间的 key, 如果这些 key 可能会在过期后的某个时间点被超高并发地访问. 这个和缓存雪崩的区别在于这里针对某一 key 缓存, 前者则是很多 key.
解决方法:
使用互斥锁来解决问题, 通俗的描述就是, 一万个用户访问了, 但是只有一个用户可以拿到访问数据库的权限, 当这个用户拿到这个权限之后重新创建缓存, 这个时候剩下的访问者因为没有拿到权限, 就原地等待着去访问缓存.
数据一致性
数据不一致的几种情况:
数据库有数据, 缓存没有数据;
数据库有数据, 缓存也有数据, 数据不相等;
数据库没有数据, 缓存有数据.
目前比较常用的数据缓存策略的是 Cache Aside Pattern, 更新缓存是先把数据存到数据库中, 成功后, 再让缓存失效.
这种策略下不一致产生的原因只有更新数据库成功, 但是删除缓存失败.
解决方案:
对删除缓存进行重试.
定期全量更新缓存.
合理设置缓存过期时间.
使用内置 MemoryCache
ASP.NET Core 支持多种不同的缓存. 包括内存缓存, 分布式缓存 (Redis 和 SQL Server).Github 开源地址 Libraries for in-memory caching and distributed caching. https://github.com/aspnet/Caching
IMemoryCache 是把数据存储在 web 服务器的内存中.
在 ConfigureServices 中调用 AddMemoryCache 通过依赖关系注入引用服务.
- public void ConfigureServices(IServiceCollection services)
- {
- services.AddMemoryCache();
- services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
- }
在控制器类中用构造器注入的方式创建 IMemoryCache 的对象.
- using Microsoft.Extensions.Caching.Memory;
- public class ValuesController : ControllerBase
- {
- private IMemoryCache _cache;
- public ValuesController(IMemoryCache cache)
- {
- _cache = cache;
- }
- }
一些常用操作
创建缓存
- Set()
- DateTime cacheEntry1 = DateTime.Now;
- var cacheEntryOptions = new MemoryCacheEntryOptions().SetAbsoluteExpiration(TimeSpan.FromSeconds(3));
- _cache.Set("cache1", cacheEntry1, cacheEntryOptions);
- GetOrCreate() GetOrCreateAsync
- var cacheEntry = _cache.GetOrCreate("cache1", entry =>
- {
- entry.SetAbsoluteExpiration(TimeSpan.FromSeconds(3));
- return DateTime.Now;
- });
获取缓存
- Get()
- var cacheEntry = this._cache.Get<DateTime?>("cache1");
- TryGetValue()
- DateTime cacheEntry;
- if (!_cache.TryGetValue("cache1", out cacheEntry))
- {
- // Key not in cache, so get data.
- cacheEntry = DateTime.Now;
- var cacheEntryOptions = new MemoryCacheEntryOptions()
- .SetAbsoluteExpiration(TimeSpan.FromSeconds(3));
- _cache.Set("cache1", cacheEntry, cacheEntryOptions);
- }
删除缓存
- Remove()
- _cache.Remove("cache1");
其他知识点
ICacheEntry 成员:
Key 缓存 key
Value 缓存值
AbsoluteExpiration 绝对过期时间, 为 null 则条件无效
AbsoluteExpirationRelativeToNow
相对当前时间的绝对过期时间 (使用 TimeSpan), 为 null 条件无效
SlidingExpiration 滑动过期时间
ExpirationTokens 提供用来自定义缓存过期
PostEvictionCallbacks
缓存失效回调
Priority 缓存项优先级 (在缓存满载的时候绝对清除的顺序)
Size 代表缓存数据的大小, 在内存缓存中一般为 null
缓存过期的方式
绝对到期 (指定在一个固定的时间点到期)
滑动到期 (在一个时间长度内没有被命中则过期, 如果命中则顺延)
到期 Token(自定义过期)
使用分布式缓存 Redis
Nuget 安装 Microsoft.Extensions.Caching.Redis
ConfigureServices 方法里面添加服务 AddDistributedRedisCache
- public void ConfigureServices(IServiceCollection services)
- {
- services.AddDistributedRedisCache(options => {
- options.Configuration = "localhost";
- options.InstanceName = "Instance1";
- });
- services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
- }
常用操作
RedisCache 实现了 IDistributedCache 接口, 提供了常用的添加, 检索和删除操作.
Get,GetAsync 采用字符串键并以 byte[] 形式检索缓存项 (如果在缓存中找到)
Set,SetAsync 使用字符串键向缓存添加项 byte[] 形式
Refresh,RefreshAsync 根据键刷新缓存中的项, 并重置其可调过期超时值 (如果有)
Remove,RemoveAsync 根据键删除缓存项
- var now = DateTime.Now;
- var cacheValue = System.Text.Encoding.UTF8.GetBytes(now.ToString());
- var options = new DistributedCacheEntryOptions().SetAbsoluteExpiration(TimeSpan.FromSeconds(3));
- _cache.Set("cache1", cacheValue, options);
- _cache.Refresh("cache1");
- var value = _cache.Get("cache1");
- var nowString = System.Text.Encoding.UTF8.GetString(value);
- _cache.Remove("cache1");
由于自带的 RedisCache 继承 IDistributedCache 接口并没有提供 Redis 的一些高级特性比如 Hash, List, Set 等.
使用 Stackexchange.Redis 自己封装一个 RedisHelper 类
基于 Stackexchange.Redis https://github.com/StackExchange/StackExchange.Redis 封装一个简单 RedisHelper 类:
RedisHelper 类
- public class RedisHelper
- {
- private readonly RedisOptions _options;
- private readonly Lazy<ConnectionMultiplexer> _connectionMultiplexer;
- public RedisHelper(IOptions<RedisOptions> optionsAccessor)
- {
- if (optionsAccessor == null)
- {
- throw new ArgumentNullException(nameof(optionsAccessor));
- }
- _options = optionsAccessor.Value;
- _connectionMultiplexer = new Lazy<ConnectionMultiplexer>(CreateConnectionMultiplexer);
- }
- public IDatabase GetDatabase()
- {
- return _connectionMultiplexer.Value.GetDatabase();
- }
- private ConnectionMultiplexer CreateConnectionMultiplexer()
- {
- if (_options.ConfigurationOptions != null)
- {
- return ConnectionMultiplexer.Connect(_options.ConfigurationOptions);
- }
- else
- {
- return ConnectionMultiplexer.Connect(_options.Configuration);
- }
- }
- }
RedisOptions 配置类
- public class RedisOptions : IOptions<RedisOptions>
- {
- /// <summary>
- /// The configuration used to connect to Redis.
- /// </summary>
- public string Configuration { get; set; }
- /// <summary>
- /// The configuration used to connect to Redis.
- /// This is preferred over Configuration.
- /// </summary>
- public ConfigurationOptions ConfigurationOptions { get; set; }
- /// <summary>
- /// The Redis instance name.
- /// </summary>
- public string InstanceName { get; set; }
- RedisOptions IOptions<RedisOptions>.Value
- {
- get { return this; }
- }
- }
RedisHelperServiceCollectionExtensions 扩展类
- public static class RedisHelperServiceCollectionExtensions
- {
- public static IServiceCollection AddRedisHelper(this IServiceCollection services, Action<RedisOptions> setupAction)
- {
- if (services == null)
- {
- throw new ArgumentNullException(nameof(services));
- }
- if (setupAction == null)
- {
- throw new ArgumentNullException(nameof(setupAction));
- }
- services.AddOptions();
- services.Configure(setupAction);
- services.AddSingleton<RedisHelper>();
- return services;
- }
- }
在 ConfigureServices 里面添加服务引用
- public void ConfigureServices(IServiceCollection services)
- {
- services.AddDistributedRedisCache(options => {
- options.Configuration = "localhost"; //Configuration.GetConnectionString("RedisConnectionString");
- options.InstanceName = "master";
- });
- services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
- }
在 Controller 里面使用
- public class ValuesController : ControllerBase
- {
- private readonly RedisHelper _redisHelper;
- public ValuesController(RedisHelper redisHelper)
- {
- _redisHelper = redisHelper;
- }
- // GET api/values
- [HttpGet]
- public ActionResult<IEnumerable<string>> Get()
- {
- _redisHelper.GetDatabase().StringSet("test_key_2", "test_value_2", TimeSpan.FromSeconds(60));
- return new string[] { "value1", "value2" };
- }
- }
网上一些开源的项目提供了 Redis 支持:
- https://github.com/imperugo/StackExchange.Redis.Extensions
- https://github.com/dotnetcore/EasyCaching
来源: https://www.cnblogs.com/royzshare/p/9474740.html