C# 实操高并发分布式缓存解决方案

embedded/2024/10/25 15:39:40/

1. CAP 原则

CAP 原则也称为布鲁尔定理,由 Eric Brewer 在 2000 年提出,描述了分布式系统中的三个核心属性:一致性(Consistency)、可用性(Availability)、分区容错性(Partition Tolerance)。CAP 原则指出在分布式系统中,无法同时保证这三个属性,最多只能满足其中的两个。

  • 一致性(Consistency):系统对外表现为单个节点上的数据总是最新的,所有读请求都能获取到最近一次写入的数据。例如,像银行的交易系统,这种系统必须保持严格的一致性。

  • 可用性(Availability):系统能够始终对请求做出响应,即使部分节点故障,系统依然可以继续服务。例如,像亚马逊和谷歌这样的电商和搜索引擎系统,即使部分服务器出现问题,依然能保证大部分用户的访问。

  • 分区容错性(Partition Tolerance):当分布式系统的不同节点之间发生网络分区时,系统能够继续工作,而不发生崩溃或错误。例如,跨地域的分布式数据库系统需要在网络分区的情况下,仍保持系统的高可用性。

实际案例

  • 一致性优先:像银行系统、股票交易系统,这些系统需要确保每次查询的结果都是准确无误的,所以更注重数据一致性。
  • 可用性优先:像电商、视频网站等,在这种场景下,哪怕数据可能不是最新的,也需要保证系统的响应速度和用户体验。
  • 分区容错性优先:跨地域的社交网络、全球范围内的支付系统等,由于其涉及多个地理区域,网络延迟和网络分区问题普遍存在,系统需要具备分区容错性。

2. 实战准备

我们将创建一个 MySQL 数据库和 Redis 缓存,并设计两个操作:

  • 更新数据:数据库和缓存都需要更新。
  • 查询数据:优先从缓存中查询,如果缓存不存在,再从数据库中查询。
  • 删除缓存:当缓存过期时或数据被更新时,需要删除缓存
示例准备:
// 安装 MySql.Data 和 StackExchange.Redisusing MySql.Data.MySqlClient;
using StackExchange.Redis;
using System;
using System.Threading;class CacheWithDatabase
{private static MySqlConnection dbConnection;private static ConnectionMultiplexer redisConnection;private static IDatabase redisCache;static void Main(string[] args){// 初始化数据库连接string dbConnectionString = "Server=localhost;Database=testdb;Uid=root;Pwd=password;";dbConnection = new MySqlConnection(dbConnectionString);dbConnection.Open();// 初始化 Redis 连接redisConnection = ConnectionMultiplexer.Connect("localhost");redisCache = redisConnection.GetDatabase();// 模拟缓存与数据库的操作UpdateData("key1", "new value");string value = GetData("key1");Console.WriteLine("Retrieved value: " + value);// 删除缓存DeleteCache("key1");}// 更新数据方法static void UpdateData(string key, string newValue){// 更新数据库string query = "UPDATE test_table SET value = @newValue WHERE key = @key";using (MySqlCommand cmd = new MySqlCommand(query, dbConnection)){cmd.Parameters.AddWithValue("@newValue", newValue);cmd.Parameters.AddWithValue("@key", key);cmd.ExecuteNonQuery();}// 更新缓存redisCache.StringSet(key, newValue);Console.WriteLine("Updated cache with key: " + key);}// 查询数据方法static string GetData(string key){// 先查询缓存string cachedValue = redisCache.StringGet(key);if (cachedValue != null){Console.WriteLine("Cache hit: " + key);return cachedValue;}// 如果缓存没有命中,则查询数据库string query = "SELECT value FROM test_table WHERE key = @key";using (MySqlCommand cmd = new MySqlCommand(query, dbConnection)){cmd.Parameters.AddWithValue("@key", key);string dbValue = (string)cmd.ExecuteScalar();if (dbValue != null){redisCache.StringSet(key, dbValue); // 将数据缓存Console.WriteLine("Cache miss. Database hit: " + key);return dbValue;}return null;}}// 删除缓存方法static void DeleteCache(string key){redisCache.KeyDelete(key);Console.WriteLine("Cache deleted for key: " + key);}
}

3. 缓存更新策略分析

缓存更新策略在高并发场景下尤为重要,避免不一致性和性能瓶颈的挑战,常见的缓存更新策略有以下几种:

  • 写回缓存:在写数据时同时更新缓存和数据库。
  • 缓存失效缓存与数据库更新保持异步,数据变更时仅删除缓存,让后续查询自行更新。
  • 定时刷新:定期刷新缓存的数据。

4. 方案 1 - 先更新缓存,再更新数据库

在此方案中,首先更新缓存,再更新数据库,这种方式可以保证较高的系统性能,因为用户查询时可以快速获得缓存中的最新数据。

多线程示例

static void UpdateDataWithPriority(string key, string newValue)
{Thread cacheThread = new Thread(() => {// 更新缓存redisCache.StringSet(key, newValue);Console.WriteLine("Cache updated with priority for key: " + key);});Thread dbThread = new Thread(() =>{// 更新数据库string query = "UPDATE test_table SET value = @newValue WHERE key = @key";using (MySqlCommand cmd = new MySqlCommand(query, dbConnection)){cmd.Parameters.AddWithValue("@newValue", newValue);cmd.Parameters.AddWithValue("@key", key);cmd.ExecuteNonQuery();}Console.WriteLine("Database updated for key: " + key);});cacheThread.Start();dbThread.Start();cacheThread.Join();dbThread.Join();
}

5. 方案 2 - 先更新数据库,再更新缓存

此策略的优点是保证数据持久化安全性,先将数据存入数据库,减少丢失数据的风险。

static void UpdateDataAfterDB(string key, string newValue)
{Thread dbThread = new Thread(() =>{// 更新数据库string query = "UPDATE test_table SET value = @newValue WHERE key = @key";using (MySqlCommand cmd = new MySqlCommand(query, dbConnection)){cmd.Parameters.AddWithValue("@newValue", newValue);cmd.Parameters.AddWithValue("@key", key);cmd.ExecuteNonQuery();}Console.WriteLine("Database updated for key: " + key);});Thread cacheThread = new Thread(() => {// 更新缓存redisCache.StringSet(key, newValue);Console.WriteLine("Cache updated after database update for key: " + key);});dbThread.Start();dbThread.Join();  // 确保数据库先更新cacheThread.Start();
}

6. 方案 3 - 先删除缓存,再更新数据库

static void UpdateAfterCacheDeletion(string key, string newValue)
{Thread cacheDeletionThread = new Thread(() => {// 删除缓存redisCache.KeyDelete(key);Console.WriteLine("Cache deleted for key: " + key);});Thread dbUpdateThread = new Thread(() =>{// 更新数据库string query = "UPDATE test_table SET value = @newValue WHERE key = @key";using (MySqlCommand cmd = new MySqlCommand(query, dbConnection)){cmd.Parameters.AddWithValue("@newValue", newValue);cmd.Parameters.AddWithValue("@key", key);cmd.ExecuteNonQuery();}Console.WriteLine("Database updated for key: " + key);});cacheDeletionThread.Start();dbUpdateThread.Start();cacheDeletionThread.Join();dbUpdateThread.Join();
}

7. 方案 4 - 先更新数据库,再删除缓存

static void UpdateAfterDBThenDeleteCache(string key, string newValue)
{Thread dbUpdateThread = new Thread(() =>{// 更新数据库string query = "UPDATE test_table SET value = @newValue WHERE key = @key";using (MySqlCommand cmd = new MySqlCommand(query, dbConnection)){cmd.Parameters.AddWithValue("@newValue", newValue);cmd.Parameters.AddWithValue("@key", key);cmd.ExecuteNonQuery();}Console.WriteLine("Database updated for key: " + key);});Thread cacheDeletionThread = new Thread(() => {// 删除缓存redisCache.KeyDelete(key);Console.WriteLine("Cache deleted for key: " + key);});dbUpdateThread.Start();dbUpdateThread.Join();  // 确保数据库更新后再删除缓存cacheDeletionThread.Start();
}

8. 最终方案

结合上述方案,在高并发场景下,我们倾向于采用先更新数据库,再删除缓存的方式。这样可以保证数据的一致性,避免缓存中的脏数据,同时提升系统性能。以下是方案的流程图。

流程图
在这里插入图片描述

上面是基于高并发分布式缓存更新策略的流程图。此方案遵循先更新数据库,再删除缓存的逻辑,并采用多线程处理。在高并发情况下,确保了数据库和缓存的一致性及系统的高可用性。


http://www.ppmy.cn/embedded/132363.html

相关文章

蓝桥杯注意事项

蓝桥杯注意事项 比赛注意事项 能暴力枚举就暴力枚举,能用简单的思路做就尽量用简单的思路做。认真审核题目的题意和输入输出的要求,避免因为误解题意而导致题目错误。对于提供多组测试样例或者需要对一个过程重复进行循环的代码,要时刻记住…

如何设计数据库产品中数据备份与恢复(Backup and Recovery)功能

数据库中的数据备份与恢复(Backup and Recovery)功能是确保数据持久性、完整性和可用性的关键设计。这一功能的主要目标是防止数据丢失,支持数据库在系统故障、硬件崩溃、软件错误或人为失误时的恢复。备份与恢复设计通常围绕数据的可用性、完…

nuScenes数据集使用的相机的外参和内参

因为需要用不同数据集测试对比效果,而一般的模型代码里实现的检测结果可视化都是使用open3d的Visualizer在点云上画的3d框,展示出来的可视化效果很差,可能是偷懒,没有实现将检测结果投影到各相机的图像上,所以检测效果…

Pr 视频效果:自动重构

视频效果/变换/自动重构 Transform/Auto Reframe 自动重构 Auto Reframe效果是用于快速调整视频素材以适应不同长宽比的一项强大工具。 随着各种平台和设备的多样化,视频内容需要适应不同的屏幕尺寸和比例,如 16:9(横屏)、9:16&am…

网络安全——防火墙技术

目录 前言基本概念常见防火墙技术防火墙的主要功能防火墙的不足之处相关题目1.组织外部未授权用户访问内部网络2.DMZ区3.包过滤防火墙和代理服务防火墙 前言 这是在软件设计师备考时编写的资料文章,相关内容偏向软件设计师 基本概念 防火墙技术是网络安全领域中的…

近似线性可分支持向量机的原理推导

近似线性可分的意思是训练集中大部分实例点是线性可分的,只是一些特殊实例点的存在使得这种数据集不适用于直接使用线性可分支持向量机进行处理,但也没有到完全线性不可分的程度。所以近似线性可分支持向量机问题的关键就在于这些少数的特殊点。 相较于…

【微信小程序_16_上拉触底相关功能补充】

摘要:本文主要介绍了微信小程序开发中与数据加载相关的两个功能点: (1)添加 loading提示效果 在数据加载时,使用wx.showLoading()展示加载提示,如 wx.showLoading({ title: 数据加载中... });。 数据请求完成后,使用wx.hideLoading()隐藏加载提示。 (2)对上拉触底进行节流…

JavaScript 的 axios 实现文件下载功能

用 JavaScript 的 axios 实现文件下载功能,咱们要分几个步骤来搞定它!最主要的部分是处理 二进制数据,可以生成一个进度检测,然后把它保存为文件。 文件名的获取二进制数据获取创建下载链接 const axios require(axios);const g…