.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 ...
随机推荐
- Mac OS 终端利器 iTerm2(怕以后找不到,自存自用)
之前一直使用 Mac OS 自带的终端,用起来虽然有些不太方便,但总体来说还是可以接受的,是有想换个终端的想法,然后今天偶然看到一个终端利器 iTerm2,发现真的很强大,也非常的好用,按照网上配置了 ...
- 在Kubernetes上运行有状态应用:从StatefulSet到Operator
一开始Kubernetes只是被设计用来运行无状态应用,直到在1.5版本中才添加了StatefulSet控制器用于支持有状态应用,但它直到1.9版本才正式可用.本文将介绍有状态和无状态应用,一个通过K ...
- cas的客户端应用是负载均衡,单点退出怎么办?
之前的项目一直是单节点,这次在生产系统中使用了负载均衡,一个应用部署了两个节点,负载均衡策略未知.这样在使用时发现了这么一个问题:在单点退出后,应用有时候可以退出,但有时还在登陆状态,这就很郁闷了. ...
- Machine Learning In Action 第二章学习笔记: kNN算法
本文主要记录<Machine Learning In Action>中第二章的内容.书中以两个具体实例来介绍kNN(k nearest neighbors),分别是: 约会对象预测 手写数 ...
- 压缩打包介绍、gzip、bzip2、xz压缩工具
第5周第1次课(4月16日) 课程内容: 6.1 压缩打包介绍6.2 gzip压缩工具6.3 bzip2压缩工具6.4 xz压缩工具 6.1 压缩打包介绍 为什么要给文件进行压缩呢?首先压缩和不压缩空 ...
- 机器学习十大算法总览(含Python3.X和R语言代码)
引言 一监督学习 二无监督学习 三强化学习 四通用机器学习算法列表 线性回归Linear Regression 逻辑回归Logistic Regression 决策树Decision Tree 支持向 ...
- vue实例化过程
我们在用vue进行开发项目时,是否存在疑惑,new Vue(xxx)的过程中,究竟发生了什么?定义的数据,是如何绑定到视图上的?本篇主要介绍在实例化vue时,主要做了哪些事,文章比较长,主要篇幅内容为 ...
- input 输入框 只能输入数字、字母、汉字等
1.文本框只能输入数字代码(小数点也不能输入) <input onkeyup="this.value=this.value.replace(/\D/g,'')" onafte ...
- 如何编写可怕的Java代码?
我决定告诉你如何编写可怕的Java代码.如果你厌倦了所有这些美丽的设计模式和最佳实践,并且想写些疯狂的东西,请继续阅读. 如果你正在寻找有关如何编写良好代码的建议,请查看其它文章! 对一切使用异常 你 ...
- 论文阅读 | Sentence-BERT: Sentence Embeddings using Siamese BERT-Networks
简述 在文本语义相似度等句子对的回归任务上,BERT , RoBERTa 拿到sota. 但是,它要求两个句子都被输入到网络中,从而导致巨大开销:从10000个句子集合中找到最相似的sentence- ...