.NET+SqlServer 實現數據讀寫分離


如今,我們操作數據庫一般用ORM框架

現在用.NET Core + EFCore + SqlServer 實現數據讀寫分離

介紹

為什么要讀寫分離?

降低數據庫服務器的壓力

如何實現讀寫分離?

1.一個主庫多個從庫

2.配置主庫復制數據到從庫

為什么一個主庫多個從庫?

一般查詢多於增刪改,這就是我們常說的二八原則,20%操作是增刪改,80%操作是查詢

是否有缺點?

有延遲

如何解決延遲問題?

比較及時性的數據還是通過主庫查詢

具體如何實現?

通過發布服務器,主庫發布,而從庫訂閱,從而實現主從庫

實現

SqlServer 實現

1.使用SqlServer 2019,新建一個主庫,創建表,再通過本地發布創建發布,

然后通過本地訂閱訂閱主庫,創建兩個從庫

2.配置AlwaysOn高可用性 

 

 https://www.cnblogs.com/chenmh/p/4484176.html

.NET Core MVC項目實現

項目結構

首先,在appsettings.json配置數據庫連接字符串

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*",
  "ConnectionStrings": {
    "EFCoreTestToRead": "Server=GREAMBWANG-DC\\MSSQLSERVER2019;Database=EFCoreTestToRead01;Trusted_Connection=True;,Server=GREAMBWANG-DC\\MSSQLSERVER2019;Database=EFCoreTestToRead02;Trusted_Connection=True;",
    "EFCoreTestToWrite": "Server=GREAMBWANG-DC\\MSSQLSERVER2019;Database=EFCoreTest;Trusted_Connection=True;"
  }
}

Models層實現

創建模型

public class UserInfo
{
    [Key]
    public int Id { get; set; }
    public string Name { get; set; }
    public int Age { get; set; }
 
}

創建上下文

public class EFCoreContext : DbContext
{
    public EFCoreContext(string connectionString)
    {
        ConnectionString = connectionString;
 
        //創建數據庫
        //Database.EnsureCreated();
    }
 
    private string ConnectionString { get; }
 
    public DbSet<UserInfo> UserInfo { get; set; }
 
    /// <summary>
    /// 配置連接數據庫
    /// </summary>
    /// <param name="optionsBuilder"></param>
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        //base.OnConfiguring(optionsBuilder);
        optionsBuilder.UseSqlServer(ConnectionString);
 
    
    }
 
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        //base.OnModelCreating(modelBuilder); 
        
        //初始化數據
        modelBuilder.Entity<UserInfo>().HasData(new List<UserInfo>()
        {
            new UserInfo() { Id = 1, Name = "哈哈", Age = 17 },
            new UserInfo() { Id = 2, Name = "呵呵", Age = 18 },
            new UserInfo() { Id = 3, Name = "嘻嘻", Age = 19 }
        });
    }
}

創建上下文工廠

讀寫枚舉

public enum WriteAndReadEnum
{
    Write,
    Read
}

接口

public interface IDbContextFactory
{
    EFCoreContext CreateContext(WriteAndReadEnum writeAndRead);
}

實現

在實現數據查詢上,可以使用不同的策略,一般有隨機策略,權重策略,輪詢策略

隨機策略:隨機選擇一個從庫進行查詢

權重策略:根據權重比例選擇從庫查詢

輪詢策略:根據順序選擇從庫查詢

Models層完成

 

public class DbContextFactory : IDbContextFactory
{
    private IConfiguration Configuration { get; }
    private string[] ReadConnectionStrings;
    public DbContextFactory(IConfiguration configuration)
    {
        Configuration = configuration;
        ReadConnectionStrings = Configuration.GetConnectionString("EFCoreTestToRead").Split(",");
    }
 
    public EFCoreContext CreateContext(WriteAndReadEnum writeAndRead)
    {
        string connectionString = string.Empty;
        switch (writeAndRead)
        {
            case WriteAndReadEnum.Write:
                connectionString = Configuration.GetConnectionString("EFCoreTestToWrite");
                break;
            case WriteAndReadEnum.Read:
                connectionString = GetReadConnectionString();
                break;
            default:
                break;
        }
        return new EFCoreContext(connectionString);
    }
 
    private string GetReadConnectionString()
    {
        /*
         * 隨機策略
         * 權重策略
         * 輪詢策略
         */
 
        //隨機策略
        string connectionString = ReadConnectionStrings[new Random().Next(0, ReadConnectionStrings.Length)];
 
        return connectionString;
    }
}

在Web層中

在Startup的ConfigureServices方法添加依賴注入

services.AddScoped<IDbContextFactory, DbContextFactory>();

操作

 

public class HomeController : Controller
{
    private readonly ILogger<HomeController> _logger;
 
    public IDbContextFactory DbContextFactory { get; }
 
    public HomeController(ILogger<HomeController> logger,IDbContextFactory dbContextFactory)
    {
        _logger = logger;
        DbContextFactory = dbContextFactory;
    }
 
    public IActionResult Index()
    {
        //寫入操作
        EFCoreContext writeContext = DbContextFactory.CreateContext(WriteAndReadEnum.Write);
        writeContext.UserInfo.Add(new UserInfo() { Name = "AA", Age = 20 });
        writeContext.SaveChanges();
 
        //查詢操作
        EFCoreContext readContext = DbContextFactory.CreateContext(WriteAndReadEnum.Read);
        UserInfo userInfo = readContext.UserInfo.OrderByDescending(u => u.Id).FirstOrDefault();
 
        return View();
    }
}

總結

sqlserver讀寫分離后,讀庫和主庫會有一定時間的延遲,當業務邏輯是增加記錄后馬上刷新列表之類的邏輯時

alwayson ?
其實這個延遲一般情況下不會太大, 一般幾十ms 到 幾百 ms. 也可以在增加記錄之后 Thread.Sleep(1000) 休眠一秒, 然后再刷新列表, 問題不大的。
特別重要的(關系到錢)的業務, 就只能直接讀主庫了。

要是不容許有不一致性,那你就別分離了。

但是也可以  業務邏輯是增加記錄后馬上刷新列表之類的邏輯時-->新增成功之后將數據添加到本地集合(BindingList/ObservableCollection)這樣能將結果通知到UI列表顯示,而不是再從新讀取全部數據。

實際上讀寫分離相當程度上是增加了問題、增加了困難,而不是減少了問題。真正應該首先做到的是,盡量在邏輯中避免使用事務概念、甚至避免使用關系數據庫,盡量將面向數據庫增刪改查的程序改為面向(幾百萬)微服務的並發程序。然而很多人沒有這方面的理解,認為把數據庫分庫分表讀寫分離這類“優化”最簡單,所以一味地浪費精力在看似最簡單實則已經逐步脫離時代的優化概念上。

分離數據庫不是什么好主意,甚至可以說是“坑爹”的。現在的大規模高性能的程序,實際上是去掉增刪改查數據庫層概念,而是面向全局一致性分布式數據緩存層來編程。

高性能系統是去低級的數據庫層,而是使用中間件層,也就是在后台業務邏輯處理中也要至少三層設計。

具備維護功能的代碼本身應該是讀寫在寫的數據庫上,而不是寫在寫數據庫,而讀在讀數據庫

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM