惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

V
Visual Studio Blog
爱范儿
爱范儿
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
雷峰网
雷峰网
V
V2EX
博客园_首页
Engineering at Meta
Engineering at Meta
博客园 - 聂微东
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Apple Machine Learning Research
Apple Machine Learning Research
GbyAI
GbyAI
H
Help Net Security
A
About on SuperTechFans
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Blog — PlanetScale
Blog — PlanetScale
W
WeLiveSecurity
云风的 BLOG
云风的 BLOG
D
Docker
Security Archives - TechRepublic
Security Archives - TechRepublic
Help Net Security
Help Net Security
N
News and Events Feed by Topic
Simon Willison's Weblog
Simon Willison's Weblog
G
Google Developers Blog
A
Arctic Wolf
T
The Blog of Author Tim Ferriss
博客园 - 叶小钗
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
Google DeepMind News
Google DeepMind News
博客园 - 三生石上(FineUI控件)
aimingoo的专栏
aimingoo的专栏
Hacker News: Ask HN
Hacker News: Ask HN
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
博客园 - 司徒正美
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
P
Privacy International News Feed
T
Troy Hunt's Blog
T
Tenable Blog
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
Recent Commits to openclaw:main
Recent Commits to openclaw:main
Recorded Future
Recorded Future
F
Fortinet All Blogs
D
DataBreaches.Net
B
Blog
T
Threat Research - Cisco Blogs
MyScale Blog
MyScale Blog
Hacker News - Newest:
Hacker News - Newest: "LLM"
The GitHub Blog
The GitHub Blog
Security Latest
Security Latest
M
MIT News - Artificial intelligence

博客园 - 郎中令

锦衣夜行,AI乐园 Redis初体验: 搭建与简单应用(一) .Net Core的SwaggerUI接口分类+固定路由 真实性编码:配置NLog日志+Seq分布式 防御性编码:手搓NLog日志+Seq分布式 对接Java所谓的DES加解密 人大金仓数据库转换 人大金仓踩坑指南 RSA加解密笔记 HTTPS请求笔记- SSL安全通道验证问题 .NetCore打包部署(DLL) 字典映射处理 SM4算法快速预览与Framework4.5版本对接 简易首页防暴力-字典计时器 vite运行打包前端-部署Linux 记一次线上数据库异常的协助排查 降本增笑:记一次首页并发白屏优化过程 solr 基础介绍以及踩坑日记 动态展示缩放背景图
Redis实战:用缓存为数据库减负(二)
郎中令 · 2025-09-03 · via 博客园 - 郎中令

     上一篇记录了redis 的搭建、配置、服务注册  , 本篇来记录将redis 接入项目中的部分业务,在业务快速迭代过程中,数据库连接数和磁盘 IO 逐渐成为性能瓶颈,   并发高峰时,连接池耗尽导致请求排队,以及重复的数据库查询把磁盘 IO 打满,RT 飙高。因此引入 Redis 作为“高速缓存层”,目标是:

  1. 减少数据库连接数——把热点查询拦截在缓存层;

  2. 降低磁盘 IO——把读操作从磁盘搬到内存;

  3. 提升吞吐量——单实例 Redis QPS ≈ 10 万,远高于 MySQL。

第一步, 将redis 的nuget包 安装到项目中,我这里选择的时公共层Comm,方便后续在redis帮助类中书写基本方法

image

 第二步,编写基础的redis帮助类,如果是单机,直接食用即可,支持重连,空数据、连接失败、异常都会返回null,业务只需要根据是否为空来判断即可,不会阻塞原有业务,如果项目部署采用是分布式,要考虑到集群或者哨兵模式

 public static class APIRedisHelper
 {
     #region 私有成员
     // 连接字符串(保持你的原逻辑)
     private static readonly Lazy<ConnectionMultiplexer?> _connLazy = new(() =>
     {
         try
         {
             return ConnectionMultiplexer.Connect(GetConnectionString());
         }
         catch
         {
             // 首次连接失败后先返回 null
             return null;
         }
     });

     // volatile 保证可见性;用于重连后替换实例
     private static volatile ConnectionMultiplexer? _current;
     private static readonly SemaphoreSlim _lock = new(1, 1);

     private static IDatabase? Db => _current?.GetDatabase() ?? _connLazy.Value?.GetDatabase();

     public static TimeSpan ExpireTime = TimeSpan.FromDays(60);

     private static string? GetConnectionString()
     {
         return new ConfigurationBuilder()
                .SetBasePath(Directory.GetCurrentDirectory())
                .AddJsonFile("appsettings.json")
                .Build()["Connection:APIRedisConnectionString"]?.Trim();
     }

     /// <summary>
     /// 获取当前可用的 Database 实例,如果已断开则尝试重连一次。
     /// 整个重连过程是异步的,但调用方不会阻塞(快速失败)。
     /// </summary>
     private static async Task<IDatabase?> GetDbAsync()
     {
         var old = _current ?? _connLazy.Value;
         if (old is null || !old.IsConnected)
         {
             // 双重检查锁,确保只会有一个线程去重连
             await _lock.WaitAsync().ConfigureAwait(false);
             try
             {
                 // 再次判断,防止并发重连
                 var again = _current ?? _connLazy.Value;
                 if (again is null || !again.IsConnected)
                 {
                     again?.Dispose(); // 释放旧连接
                     try
                     {
                         var mux = await ConnectionMultiplexer.ConnectAsync(GetConnectionString()).ConfigureAwait(false);
                         _current = mux; // 覆盖实例
                     }
                     catch (Exception ex)
                     {
                         // 记录日志后仍返回 null(保持你的异常处理风格)
                         NLogHelper.WriteError($"Redis 重连失败: {ex.Message}-{ex.StackTrace}");
                         _current = null;
                     }
                 }
             }
             finally
             {
                 _lock.Release();
             }
         }
         return (_current ?? _connLazy.Value)?.GetDatabase();
     }

     private static async Task<T> SafeExecuteAsync<T>(Func<IDatabase, Task<T>> redisFunc, T defaultValue = default!)
     {
         try
         {
             var db = await GetDbAsync().ConfigureAwait(false);
             return db is null ? defaultValue : await redisFunc(db).ConfigureAwait(false);
         }
         catch (Exception ex)
         {
             NLogHelper.WriteError($"Redis操作失败: {ex.Message}-{ex.StackTrace}");
             return defaultValue;
         }
     }

     private static T SafeExecute<T>(Func<IDatabase, T> redisFunc, T defaultValue = default!)
     {
         try
         {
             return Db is null ? defaultValue : redisFunc(Db);
         }
         catch (Exception ex)
         {
             NLogHelper.WriteError($"Redis操作失败: {ex.Message}-{ex.StackTrace}");
             return defaultValue;
         }
     }
     #endregion

     #region 部分Key 命名
     public static string GetHTMKey(string key) => $"htm_{key}";
     public static string GetHTCKey(string key) => $"htc_{key}";
     public static string GetHTPKey(string key) => $"htp_{key}";
     public static string GetHTBKey(string key) => $"htb_{key}";
     #endregion

     #region 基础方法
     public static Task<bool> SetAsync(string key, string value, TimeSpan? expiry = null) =>
         SafeExecuteAsync(db => db.StringSetAsync(key, value, expiry ?? ExpireTime), false);

     public static Task<string?> GetAsync(string key) =>
         SafeExecuteAsync(async db => (string?)await db.StringGetAsync(key));

     public static Task<bool> DeleteAsync(string key) =>
         SafeExecuteAsync(db => db.KeyDeleteAsync(key), false);

     public static Task<bool> ExistsAsync(string key) =>
         SafeExecuteAsync(db => db.KeyExistsAsync(key), false);

     public static Task<bool> SetExpiryAsync(string key, TimeSpan expiry) =>
         SafeExecuteAsync(db => db.KeyExpireAsync(key, expiry), false);
     #endregion
 }

使用了redis之后,就要考虑到数据的强一致性,以及防止缓存被击穿,前者一般采用双删的方式来保证数据一致,后者我一般通过动态更新有效期来浅显的控制,当然也可以结合提前续期,以及增加互斥锁来防止进一步被击穿,主要看项目体量和薪资的多少来设计具体的哪种方案, 业务的简单调用如下

        private async Task<KiaserData> GetKiaserData(string addr)
        {
            try
            {
                var data = await APIRedisHelper.GetAsync($"{APIRedisHelper.GetHTMKey(addr)}");
                if (data == null)
                {
                    string str = GetKiaserData(addr);
                    var redis = GetHTMObject(str);
                    if (redis != null)
                    {
                        var task = Task.Factory.StartNew(async () =>
                        {
                            redis.Addr = addr;
                            await APIRedisHelper.SetAsync($"{APIRedisHelper.GetHTMKey(addr)}", JsonConvert.SerializeObject(redis));
                        });
                    }

                    return redis;
                }
                return JsonConvert.DeserializeObject<KiaserData>(data);
            }
            catch (Exception ex)
            {
                NLogHelper.WriteError($"获取Kiase数据请求异常,{addr}-{ex.Message}", ParamConst.KiaserHome);
            }
            return null;

        }

 这里小小的提供一个redis 的可视化工具,不是新的,只是个人用习惯了

Medis :https://github.com/sinajia/medis/releases/tag/win

image