在实际业务系统中,当单个数据库不能承载负载压力的时候,一般我们采用数据库读写分离的方式来分担数据库负载。主库承担写以及事务操作,从库承担读操作。

为了支持多种数据库我们先定义一个数据类型字典。key为连接字符串,value为数据库类型:

        /// <summary>
/// 数据库方言集合
/// </summary>
private readonly Dictionary<string, DatabaseDialectEnum> DialectDictionary
= new Dictionary<string, DatabaseDialectEnum>
{
["sqlconnection"] = DatabaseDialectEnum.MSSQL,
["sqlceconnection"] = DatabaseDialectEnum.SQLCE,
["npgsqlconnection"] = DatabaseDialectEnum.POSTGRES,
["sqliteconnection"] = DatabaseDialectEnum.SQLLITE,
["mysqlconnection"] = DatabaseDialectEnum.MYSQL,
["fbconnection"] = DatabaseDialectEnum.FIREBASE
};

这样我们切换不同的数据库只需要配置数据库连接字符串即可。

以mssql为例,配置数据库连接字符串

  "ConnectionString": {
"sqlconnection": "Data Source=.;Initial Catalog=Db;User ID=sa;Password=**;Enlist=false;Max Pool SIZE=500;Min Pool SIZE=50;MultipleActiveResultSets=True",
"sqlconnection_slaver_1": "Data Source=.;Initial Catalog=Db;User ID=sa;Password=**;Enlist=false;Max Pool SIZE=500;Min Pool SIZE=50;MultipleActiveResultSets=True",
"sqlconnection_slaver_2": "Data Source=.;Initial Catalog=Db;User ID=sa;Password=**;Enlist=false;Max Pool SIZE=500;Min Pool SIZE=50;MultipleActiveResultSets=True"
}
Key: sqlconnection为主库(master)连接字符串,Key: sqlconnection_slaver_1和sqlconnection_slaver_2为两个从库(slaver)连接字符串。多个从库(slaver)可以实现随机访问。也可以采用其他算法来负载均衡。

根据字符串连接配置我们得到 主库 连接串,和从库连接串集合。同时根据连接串的key 确定数据库种类。代码如下:
        /// <summary>
/// 主数据库连接串
/// </summary>
private string MasterConnectionString { get; set; }
/// <summary>
/// 从数据库连接串集合
/// </summary>
private List<string> SlaverConnectionStrings { get; set; } = new List<string>();
public ConnectionFactory(IConfiguration configuration, ILoggerFactory loggerFactory)
{
_logger = loggerFactory.CreateLogger<ConnectionFactory>();
var connectionKeys = configuration.GetSection("ConnectionString").GetChildren().Select(s => s.Key).ToArray();
foreach (var connKey in connectionKeys)
{
var connSplit = connKey.Split('_');
if (connSplit.Length == )
{
MasterConnectionString = configuration[$"ConnectionString:{connKey}"];
            //根据连接字符串约定,确定数据库类型
DatabaseDialect = DialectDictionary[connKey];
}
else
{
SlaverConnectionStrings.Add(configuration[$"ConnectionString:{connKey}"]);
} }
}
        /// <summary>
/// 数据库类型
/// </summary>
public DatabaseDialectEnum DatabaseDialect { get; private set; }

获取主库连接

        private IDbConnection GetMasterConnection()
{
return GetConnection(MasterConnectionString);
}

获取从库连接,这里采用随机算法,如果没有配置从库,这里会返回主库连接。

        private IDbConnection GetSlaverConnection()
{
int sc = SlaverConnectionStrings.Count();
if (sc > )
{
Random random = new Random();
int index = random.Next(, sc);
return GetConnection(SlaverConnectionStrings[index]);
}
else
{
_logger.LogInformation("没有设置从库,将建立主库连接");
return GetMasterConnection();
}
}
        private IDbConnection GetConnection(string connectionString) => DatabaseDialect switch
{
DatabaseDialectEnum.MSSQL =>new ProfiledDbConnection(new SqlConnection(connectionString),MiniProfiler.Current),
DatabaseDialectEnum.MYSQL => new ProfiledDbConnection(new MySqlConnection(connectionString), MiniProfiler.Current),
_ => throw new NotImplementedException()
};
注:这里采用MiniProfiler来监控数据库连接性能,所以 返回的connection用ProfiledDbConnection进行了包装。

主从数据源类型如下:

    public enum DataSourceEnum
{
MASTER,
SLAVE
}

本ConnectionFactory为单例模式,存在多线程访问的情况,所以数据源设置为ThreadLocal<DataSourceEnum>,线程内共享。

private static ThreadLocal<DataSourceEnum> threadLocal = new ThreadLocal<DataSourceEnum>();
/// <summary>
/// 当前线程数据源
/// </summary>
/// <param name="sourceEnum"></param>
public DataSourceEnum DataSource
{
set { threadLocal.Value = value; }
get { return threadLocal.Value; }
}

下面正式获取IDbConnection

        public IDbConnection GetDbConnection()
{
if (DataSource == DataSourceEnum.MASTER)
{
return GetMasterConnection();
}
else
{
return GetSlaverConnection();
}
}

使用:

根据文章开头所描述的实际操作来进行主从库访问。

        private IDbConnection GetDbConnection(DataSourceEnum dataSource)
{
ConnectionFactory.DataSource = dataSource;
return ConnectionFactory.GetDbConnection();
}
using var connection = GetDbConnection(DataSourceEnum.MASTER);
connection.Execute(sql, param, CurrentTransaction, null, commandType)
 using var connection = GetDbConnection(DataSourceEnum.SLAVE);
connection.Get<T>(id, CurrentTransaction, CommandTimeout)
奉上全部代码
    public class ConnectionFactory : IConnectionFactory
{
private readonly ILogger _logger;
private static ThreadLocal<DataSourceEnum> threadLocal = new ThreadLocal<DataSourceEnum>();
static ConnectionFactory()
{
//设置dapper的tableName取值
SqlMapperExtensions.TableNameMapper = (type) => type.Name;
} /// <summary>
/// 当前线程数据源
/// </summary>
/// <param name="sourceEnum"></param>
public DataSourceEnum DataSource
{
set { threadLocal.Value = value; }
get { return threadLocal.Value; }
} /// <summary>
/// 主数据库连接串
/// </summary>
private string MasterConnectionString { get; set; }
/// <summary>
/// 从数据库连接串集合
/// </summary>
private List<string> SlaverConnectionStrings { get; set; } = new List<string>();
public ConnectionFactory(IConfiguration configuration, ILoggerFactory loggerFactory)
{
_logger = loggerFactory.CreateLogger<ConnectionFactory>();
var connectionKeys = configuration.GetSection("ConnectionString").GetChildren().Select(s => s.Key).ToArray();
foreach (var connKey in connectionKeys)
{
var connSplit = connKey.Split('_');
if (connSplit.Length == )
{
MasterConnectionString = configuration[$"ConnectionString:{connKey}"];
DatabaseDialect = DialectDictionary[connKey];
}
else
{
SlaverConnectionStrings.Add(configuration[$"ConnectionString:{connKey}"]);
} }
}
/// <summary>
/// 数据库方言集合
/// </summary>
private readonly Dictionary<string, DatabaseDialectEnum> DialectDictionary
= new Dictionary<string, DatabaseDialectEnum>
{
["sqlconnection"] = DatabaseDialectEnum.MSSQL,
["sqlceconnection"] = DatabaseDialectEnum.SQLCE,
["npgsqlconnection"] = DatabaseDialectEnum.POSTGRES,
["sqliteconnection"] = DatabaseDialectEnum.SQLLITE,
["mysqlconnection"] = DatabaseDialectEnum.MYSQL,
["fbconnection"] = DatabaseDialectEnum.FIREBASE
};
/// <summary>
/// 数据库方言
/// </summary>
public DatabaseDialectEnum DatabaseDialect { get; private set; } private IDbConnection GetConnection(string connectionString) => DatabaseDialect switch
{
DatabaseDialectEnum.MSSQL =>new ProfiledDbConnection(new SqlConnection(connectionString),MiniProfiler.Current),
DatabaseDialectEnum.MYSQL => new ProfiledDbConnection(new MySqlConnection(connectionString), MiniProfiler.Current),
_ => throw new NotImplementedException()
};
public IDbConnection GetDbConnection()
{
if (DataSource == DataSourceEnum.MASTER)
{
return GetMasterConnection();
}
else
{
return GetSlaverConnection();
}
}
private IDbConnection GetMasterConnection()
{
return GetConnection(MasterConnectionString);
}
private IDbConnection GetSlaverConnection()
{
int sc = SlaverConnectionStrings.Count();
if (sc > )
{
Random random = new Random();
int index = random.Next(, sc);
return GetConnection(SlaverConnectionStrings[index]);
}
else
{
_logger.LogInformation("没有设置从库,将从建立主库连接");
return GetMasterConnection();
}
}
} public enum DataSourceEnum
{
MASTER,
SLAVE
}

.netcore实现一个读写分离的数据库访问中间件的更多相关文章

  1. Mysql读写分离——主从数据库+Atlas

    mysql集群 最近在参加项目开发微信小程序后台,由于用户数量巨大,且后台程序并不是很完美,所以对用户的体验很是不友好(简单说就是很卡).赶巧最近正在翻阅<大型网站系统与Java中间件实践> ...

  2. C#简单构架之EF进行读写分离+多数据库(Mysql/SqlService)

    最近因为项目需要,研究了下EF的读写分离,所以做了一个demo进行测试,下面是项目的结构 表现层view 主要提供Web.WebApi等表现层的解决方案 公共层public 主要提供项目公共类库,数据 ...

  3. windows NLB实现MSSQL读写分离--从数据库集群读负载均衡

    主从模式,几乎大部分出名的数据库都支持的一种集群模式. 当Web站点的访问量上去之后,很多站点,选择读写分离,减轻主数据库的的压力.当然,一主多从也可以作用多个功能,比如备份.这里主要演示如何实现从数 ...

  4. C#简单构架之EF进行读写分离+多数据库Mysql/SqlServer

    http://www.php361.com/index.php?c=index&a=view&id=3857 不建议用,太重的框架EF,仅仅参考一下别人的思路就好. [导读]最近因为项 ...

  5. 一个C#的XML数据库访问类

    原文地址:http://hankjin.blog.163.com/blog/static/33731937200942915452244/ 程序中不可避免的要用到配置文件或数据,对于数据量比较小的程序 ...

  6. 实现用一个QueryService支持多数据库访问

    上图,是在服务端定义多个数据库,准备在客户端通过“联接名称”及“客户端服务名称”访问这些数据库. 基于实现的MultiDBQueryService,将其注册为一个指定客户端服务名称的服务,如下图: 这 ...

  7. springboot+springAOP实现数据库读写分离及数据库同步(MySQL)----最新可用2019-2-14

    原文:https://blog.csdn.net/wsbgmofo/article/details/79260896 1,数据源配置文件,如下 datasource.readSize=1spring. ...

  8. EF架构~通过EF6的DbCommand拦截器来实现数据库读写分离~续~添加事务机制

    回到目录 上一讲中简单介绍了一个EF环境下通过DbCommand拦截器来实现SQLSERVER的读写分离,只是一个最简单的实现,而如果出现事务情况,还是会有一些问题的,因为在拦截器中我们手动开启了Co ...

  9. yii2操作数据库 mysql 读写分离 主从复制

    转载地址:http://www.kuitao8.com/20150115/3471.shtml 开始使用数据库首先需要配置数据库连接组件,通过添加 db 组件到应用配置实现("基础的&quo ...

随机推荐

  1. day 23 面向对象中类的成员 和嵌套

    1.类的成员? 变量.方法.属性 class Foo: # 方法 def __init__(self,name): # 实例变量/字段 self.name = name # 方法 def func(s ...

  2. hadoop全分布式的搭建

    修改主机名:vim /etc/sysconfig/network 1 修改 hadoop-env.sh 2 修改core-site.xml /hadoop/tmpdir: 产生 namenode中fs ...

  3. LNMP架构介绍、MySQL和PHP安装、Nginx介绍

     6月6日任务  12.1 LNMP架构介绍12.2 MySQL安装12.3/12.4 PHP安装12.5 Nginx介绍 扩展Nginx为什么比Apache Httpd高效:原理篇 http://w ...

  4. Python Kite 使用教程 轻量级代码提示

    1: 概述 今天升级annacoda 插件 spyder  (4.0.0 )的时候 提示安装kite ,这是什么玩意? 下载下来试一试? 原来:就是一个代码提示插件.. 说白了" 就是让开发 ...

  5. linux虚拟机中FTP本地用户模式配置流程

    1.首先在自己虚拟机中安装vsftpd服务,可以先去yum中下载(当然你要有本地yum仓库) 输入命令: yum  install  vsftpd 下载完成之后打开vsftpd服务 输入命令:syst ...

  6. linux进程间通信之共享内存学习记录

    进程 狭义定义:进程是正在运行的程序的实例(an instance of a computer program that is being executed). 广义定义:进程是一个具有一定独立功能的 ...

  7. ajax加载引起瀑布流布局堆叠

    jQuery 瀑布流使用ajax加载数据库图片url ,ajax每次请求到的数据不变时,瀑布流效果没问题. 但当请求到的数据变化时,瀑布流图片格式会重叠 或者相隔很远等等的格式问题,这是由于图片加载是 ...

  8. Spring Boot 最流行的 16 条实践解读!【华为云技术分享】

    置顶:华为云618大促火热进行中,全场1折起,免费抽主机,消费满额送P30 Pro,点此抢购. Spring Boot是最流行的用于开发微服务的Java框架.在本文中,将与大家分享自2016年以来笔者 ...

  9. Delphi - 手把手教你基于D7+Access常用管理系统架构的设计与实现 (更新中)

    前言 从事软件开发工作好多年了,学的越深入越觉得自己无知,所以还是要对知识保持敬畏之心,活到老,学到老! 健身和代码一样都不能少,身体是革命的本钱,特别是我们这种高危工种,所以小伙伴们运动起来!有没有 ...

  10. MySQL必知必会(Create, Alter)

    CREATE DATABASE mysql_crash_course_db; USE mysql_crash_course_db; CREATE TABLE customers ( cust_id i ...