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

为了支持多种数据库我们先定义一个数据类型字典。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. RobotFramework自动化测试框架-Selenium Web自动化(三)关于在RobotFramework中如何使用Selenium很全的总结(下)

    本文紧接着RobotFramework自动化测试框架-Selenium Web自动化(二)关于在RobotFramework中如何使用Selenium很全的总结(上)继续分享RobotFramewor ...

  2. Linux的curl和wget

    wget wget命令用来从指定的URL下载文件.wget非常稳定,它在带宽很窄的情况下和不稳定网络中有很强的适应性,如果是由于网络的原因下载失败,wget会不断的尝试,直到整个文件下载完毕.如果是服 ...

  3. 全球 43 亿 IPv4 地址已耗尽!IPv6,刻不容缓

    大家都知道目前网络协议使用的主要是 IPv4,全称为 Internet Protocol version 4,作用是为每一个网络和每一台主机分配一个 IP,IP 地址是一个 32 位的二进制数,算下来 ...

  4. Gitlab用户信息批量导出

    前言 因运维体系中涉及到用户权限管理及统计,需将Gitlab用户数据提取出来并录入到公司内部自建的权限统计平台. 本文将对Gitlab的用户信息数据批量导出进行操作说明! 思路 A)要对数据进行批量的 ...

  5. Flask入门学习——蓝图Blueprint

    flask蓝图可以实现应用程序的模块化,即通常作用于相同的url前缀,eg:/user/id,/user/profile等类似这样,可以放在一个模块当中,这样会让应用更加清晰便于开发与维护. 这里有个 ...

  6. 【Android - 自定义View】之自定义View浅析

    1.概述 Android自定义View / ViewGroup的步骤大致如下: 1) 自定义属性: 2) 选择和设置构造方法: 3) 重写onMeasure()方法: 4) 重写onDraw()方法: ...

  7. 关于layer的基本所有的事件全部失效问题

    只要在页面中,要是存在id="undefined", layer的基本所有的事件全部失效. <input type="radio" id="un ...

  8. 关于javascript中=的返回值

    今天看了一段代码,大概是这样的: function test(){ a=4; b=5; return b=a; } test();//返回? 返回值是多少呢?运行结果是4 这可以理解为将a的值赋给b以 ...

  9. webpack4.0各个击破(6)—— Loader篇【华为云技术分享】

    版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/devcloud/article/detai ...

  10. (转)oracle视图详解

    Oracle视图详解   一. 视图的定义 视图(view),也称虚表, 不占用物理空间,这个也是相对概念,因为视图本身的定义语句还是要存储在数据字典里的.视图只有逻辑定义.每次使用的时候,只是重新执 ...