.NET Redis 数据结构、分布式锁、缓存题目

[复制链接]
发表于 2026-5-17 17:50:34 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×
一、Redis 常用 5 个数据结构

 StackExchange.Redis 是 .NET 最主流客户端,全部结构都有对应 API。 1. String(字符串)

 用途:缓存对象、计数器、分布式锁、简单设置 
  1. // 存对象(序列化)
  2. await db.StringSetAsync("user:1", JsonSerializer.Serialize(user));
  3. // 取对象
  4. var user = JsonSerializer.Deserialize<User>(await db.StringGetAsync("user:1"));
  5. // 计数器
  6. await db.StringIncrementAsync("view:count");
复制代码
 2. Hash(哈希)

 用途:存储对象、用户信息、商品详情 
  1. await db.HashSetAsync("product:100", new HashEntry[] {
  2.     new HashEntry("name", "iPhone"),
  3.     new HashEntry("price", "5999")
  4. });
  5. var name = await db.HashGetAsync("product:100", "name");
复制代码
 3. List(列表)

 用途:消息队列、最新动态、排行榜列表 下令:LPush、RPop、LRange 4. Set(聚集)

 用途:去重、共同关注、抽奖、标签 下令:SAdd、SRem、SInter(交集) 5. SortedSet(有序聚集)

 用途:排行榜、延时使命、权重排序 下令:ZAdd、ZRangeWithScores  二、.NET Redis 分布式锁

 分布式锁 = 办理多服务 / 多实例同时利用同一资源的题目。 核心原则

 

  • 互斥:同一时间只有一个线程拿到锁
  • 防死锁:必须设置逾期时间
  • 防误删:只能删本身加的锁
  • 原子性:加锁 / 解锁必须原子利用
  .NET 尺度分布式锁代码

 
  1. /// <summary>
  2. /// 获取分布式锁
  3. /// </summary>
  4. /// <param name="lockKey">锁key</param>
  5. /// <param name="requestId">唯一请求ID(防止误删)</param>
  6. /// <param name="expireSeconds">过期时间(防死锁)</param>
  7. public async Task<bool> LockAsync(string lockKey, string requestId, int expireSeconds = 10)
  8. {
  9.     // NX = 不存在才设置 | EX = 秒级过期
  10.     return await _db.StringSetAsync(lockKey, requestId, TimeSpan.FromSeconds(expireSeconds), When.NotExists);
  11. }
  12. /// <summary>
  13. /// 释放分布式锁(Lua 保证原子性)
  14. /// </summary>
  15. public async Task UnlockAsync(string lockKey, string requestId)
  16. {
  17.     string luaScript = @"
  18.         if redis.call('get', KEYS[1]) == ARGV[1] then
  19.             return redis.call('del', KEYS[1])
  20.         else
  21.             return 0
  22.         end";
  23.     await _db.ScriptEvaluateAsync(luaScript, new RedisKey[] { lockKey }, new RedisValue[] { requestId });
  24. }
复制代码
 为什么必须用 Lua?

 由于判定锁 + 删除锁必须是原子利用,否则会出现: 线程 A 判定锁是本身 → 锁逾期 → 线程 B 加锁 → 线程 A 删除 B 的锁。  三、缓存穿透 / 缓存击穿 / 缓存雪崩(.NET 办理方案)

 这三个是高频口试题 + 高并发必踩坑。  1. 缓存穿透(查不存在的数据)

 征象:查询一条数据库根本没有的数据 → 每次都查库 → 压垮数据库。 办理方案: 

  • 缓存空值
 
  1. // 查不到数据时缓存空对象,过期时间短一点
  2. await db.StringSetAsync(key, "", TimeSpan.FromMinutes(1));
复制代码
  

  • 布隆过滤器(Bloom Filter) 提前把存在的 key 放入布隆过滤器,不存在直接返回。
  2. 缓存击穿(热门 Key 失效)

 征象:一个极高并发的 Key 逾期 → 全部哀求刹时打数据库。 办理方案: 

  • 热门 Key 永不逾期
  • 互斥锁(分布式锁) 只有一个线程去查库,其他线程等候。
 .NET 简单击穿防护逻辑: 
  1. var value = await db.StringGetAsync(key);
  2. if (!value.IsNull) return value;
  3. // 加锁
  4. if (await LockAsync(lockKey, requestId))
  5. {
  6.     try
  7.     {
  8.         // 再次检查缓存(双重检查)
  9.         value = await db.StringGetAsync(key);
  10.         if (!value.IsNull) return value;
  11.         // 查询数据库
  12.         var data = await _repo.GetData(id);
  13.         // 写缓存
  14.         await db.StringSetAsync(key, JsonSerializer.Serialize(data), TimeSpan.FromHours(1));
  15.         return data;
  16.     }
  17.     finally
  18.     {
  19.         await UnlockAsync(lockKey, requestId);
  20.     }
  21. }
  22. else
  23. {
  24.     // 等待重试
  25.     await Task.Delay(100);
  26.     return await GetCacheAsync(id);
  27. }
复制代码
   3. 缓存雪崩(大量 Key 同时逾期)

 征象:大量缓存同一时间团体失效 → 数据库压力暴增。 办理方案: 

  • 给逾期时间加随机值
 
  1. var expire = TimeSpan.FromHours(2) + TimeSpan.FromSeconds(new Random().Next(0, 600));
复制代码
  

  • 多级缓存(本地缓存 + Redis)
  • 服务熔断 / 降级
  • Redis 集群高可用(主从 + 哨兵)
  四、三者快速区分

 

  • 穿透:查不存在的数据 → 缓存永久不掷中
  • 击穿:一个热门 Key 逾期 → 并发打库
  • 雪崩:大量 Key 同时逾期 → 数据库瓦解
  总结

 

  • 5 种数据结构:String/Hash/List/Set/SortedSet,.NET 用 StackExchange.Redis 利用。
  • 分布式锁:必须满足 互斥 + 逾期 + 唯一ID + Lua解锁。
  • 缓存三大题目:

    • 穿透 → 缓存空值 / 布隆过滤器
    • 击穿 → 永不逾期 / 分布式锁
    • 雪崩 → 随机逾期 / 集群 / 多级缓存

回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表