当前位置: 首页 > article >正文

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

1. CAP 原则

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

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

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

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

实际案例

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

2. 实战准备

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

  • 更新数据:数据库和缓存都需要更新。
  • 查询数据:优先从缓存中查询,如果缓存不存在,再从数据库中查询。
  • 删除缓存:当缓存过期时或数据被更新时,需要删除缓存。
示例准备:
// 安装 MySql.Data 和 StackExchange.Redis

using 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);
    });

    dbUpdate

Thread.Start();
    dbUpdateThread.Join();  // 确保数据库更新后再删除缓存
    cacheDeletionThread.Start();
}

8. 最终方案

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

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

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


http://www.kler.cn/news/355676.html

相关文章:

  • Git中Update和Pull的区别
  • H.264 编码参数优化策略
  • 时序数据库 TDengine 支持集成开源的物联网平台 ThingsBoard
  • 计算机前沿技术-人工智能算法-大语言模型-最新研究进展-2024-10-17
  • 【计算机网络 - 基础问题】每日 3 题(四十八)
  • 简单说说 spring是如何实现AOP的(源码分析)
  • try increasing the minimum deployment target IOS
  • Trimble三维激光扫描开启工业元宇宙的安全“智造”之路-沪敖3D
  • 【PyTorch][chapter30][transformer-3]
  • Apache SeaTunnel 介绍
  • 用 Git Stash 临时保存修改,轻松切换任务!
  • 安科瑞智慧能源管理系统EMS3.0在浙江某能源集团有限公司的应用
  • Web3与传统互联网的区别
  • mqtt单次订阅多个主题
  • LeetCode146. LRU 缓存(2024秋季每日一题 37)
  • Centos7 安装升级最新版Redis7.4.1
  • 《太原理工大学学报》
  • JavaGuide(9)
  • Leetcode 最长连续有效括号
  • 服务器整合:提高数据中心效率的低成本高效策略