.netcore实现一个读写分离的数据库访问中间件
在实际业务系统中,当单个数据库不能承载负载压力的时候,一般我们采用数据库读写分离的方式来分担数据库负载。主库承担写以及事务操作,从库承担读操作。
为了支持多种数据库我们先定义一个数据类型字典。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实现一个读写分离的数据库访问中间件的更多相关文章
- Mysql读写分离——主从数据库+Atlas
mysql集群 最近在参加项目开发微信小程序后台,由于用户数量巨大,且后台程序并不是很完美,所以对用户的体验很是不友好(简单说就是很卡).赶巧最近正在翻阅<大型网站系统与Java中间件实践> ...
- C#简单构架之EF进行读写分离+多数据库(Mysql/SqlService)
最近因为项目需要,研究了下EF的读写分离,所以做了一个demo进行测试,下面是项目的结构 表现层view 主要提供Web.WebApi等表现层的解决方案 公共层public 主要提供项目公共类库,数据 ...
- windows NLB实现MSSQL读写分离--从数据库集群读负载均衡
主从模式,几乎大部分出名的数据库都支持的一种集群模式. 当Web站点的访问量上去之后,很多站点,选择读写分离,减轻主数据库的的压力.当然,一主多从也可以作用多个功能,比如备份.这里主要演示如何实现从数 ...
- C#简单构架之EF进行读写分离+多数据库Mysql/SqlServer
http://www.php361.com/index.php?c=index&a=view&id=3857 不建议用,太重的框架EF,仅仅参考一下别人的思路就好. [导读]最近因为项 ...
- 一个C#的XML数据库访问类
原文地址:http://hankjin.blog.163.com/blog/static/33731937200942915452244/ 程序中不可避免的要用到配置文件或数据,对于数据量比较小的程序 ...
- 实现用一个QueryService支持多数据库访问
上图,是在服务端定义多个数据库,准备在客户端通过“联接名称”及“客户端服务名称”访问这些数据库. 基于实现的MultiDBQueryService,将其注册为一个指定客户端服务名称的服务,如下图: 这 ...
- springboot+springAOP实现数据库读写分离及数据库同步(MySQL)----最新可用2019-2-14
原文:https://blog.csdn.net/wsbgmofo/article/details/79260896 1,数据源配置文件,如下 datasource.readSize=1spring. ...
- EF架构~通过EF6的DbCommand拦截器来实现数据库读写分离~续~添加事务机制
回到目录 上一讲中简单介绍了一个EF环境下通过DbCommand拦截器来实现SQLSERVER的读写分离,只是一个最简单的实现,而如果出现事务情况,还是会有一些问题的,因为在拦截器中我们手动开启了Co ...
- yii2操作数据库 mysql 读写分离 主从复制
转载地址:http://www.kuitao8.com/20150115/3471.shtml 开始使用数据库首先需要配置数据库连接组件,通过添加 db 组件到应用配置实现("基础的&quo ...
随机推荐
- html基础——a标签
a标签:超链接/锚点链接 实现页面跳转 只占据自己内容大小的位置 超链接: 使用 target="_self":表示在本页面跳转到 href 中的地址 target=" ...
- 【Luogu P1714】切蛋糕(面向对象编程首次尝试?)
Luogu P1714 题目的大意就是给定一个长度为n的序列,求出这个序列中长度不超过m的子串的最大和 很容易想出的一个解法就是枚举起点终点,直接暴力扫一遍得出答案. 当然也很容易发现这种做法肯定会T ...
- RocketMQ 多副本前置篇:初探raft协议
目录 1.Leader选举 1.1 一轮投票中,只有一个节点发起投票的情况 1.2 一轮投票中,超过一个节点发起投票的情况 1.3 思考如何实现Raft选主 2.日志复制 Raft协议是分布式领域解决 ...
- Java中标识符和变量的区别
1.标识符 在JAVA的组成部分中包括了对包.类.方法.变量等的起名,这些名字是要有一定的规则的: 标识符可以包含数字.字母.$._,但是不能以数字开头: 关键字不能用作标识符: 标识符是大小写敏感的 ...
- AutoCAD二次开发(2020版)--4,使用ARX向导创建编程模板(框架)--
手动创建ObjectARX应用程序非常麻烦,在此步骤中,将介绍ObjectARX向导. 在这里,我们将使用ObjectARX向导创建我们的ObjectARX应用程序. 本节的程序的需求是,接收CAD用 ...
- js的模糊查询
在项目中会用到模糊查询,之前在首页是用的element的tree显示的目录,会有用到搜索,但tree里边会有自带的模糊查询,用filter-node-method方法使用 但上次的项目中 又涉及到不试 ...
- Redis集群生产环境源码安装
安装redis集群 根据各人单位生产环境用户搭建一.安装环境 操作系统:centos7.6 关闭防火墙.关闭selinux redis1:192.168.26.128 redis2:192.1 ...
- 基于ASP.NET Core 3.0快速搭建Razor Pages Web应用
前言 虽然说学习新的开发框架是一项巨大的投资,但是作为一个开发人员,不断学习新的技术并快速上手是我们应该掌握的技能,甚至是一个.NET Framework开发人员,学习.NET Core 新框架可以更 ...
- 全面认识 RUST -- 掌控未来的雷电
文章目录 RUST 简介 如何衡量语言的好坏? 静态语言 编译器 语言定位 代表性项目 Hello World RUST 前景 RUST 简介 Rust 是一种兼顾内存安全.高并发和稳定运行的编程语言 ...
- python安装matplotlib:python -m pip install matplotlib报错
matplotlib是python中强大的画图模块. 首先确保已经安装python,然后用pip来安装matplotlib模块. 进入到cmd窗口下,建议执行python -m pip install ...