.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 ...
随机推荐
- linux目录相关操作
mkdir:新建目录 mkdir [-mp] 目录名称 -m:配置文件权限,直接设置,不需要看默认权限(umask) -p:递归创建目录 rmdir:删除空目录 rmdir [-p] 目录名称 -p: ...
- 如何理解Nginx, WSGI, Flask(Django)之间的关系
如何理解Nginx, WSGI, Flask(Django)之间的关系 值得指出的是,WSGI 是一种协议,需要区分几个相近的名词: uwsgi 同 wsgi 一样也是一种协议,uWSGI服务器正是使 ...
- 用IDCNN和CRF做端到端的中文实体识别
实体识别和关系抽取是例如构建知识图谱等上层自然语言处理应用的基础.实体识别可以简单理解为一个序列标注问题:给定一个句子,为句子序列中的每一个字做标注.因为同是序列标注问题,除去实体识别之外,相同的技术 ...
- 不服跑个分:ARM鲲鹏云服务器实战评测——华为云鲲鹏KC1实例 vs. 阿里云G5实例【华为云技术分享】
原文链接:https://m.ithome.com/html/444828.htm 今年一月份,华为正式发布了鲲鹏920数据中心高性能处理器,该处理器兼容ARM架构,采用7纳米制造,最高支持64核,主 ...
- 带着canvas去流浪系列之二 绘制折线图
[摘要] 用canvasAPI实现echarts简易图表 示例代码托管在:http://www.github.com/dashnowords/blogs 一. 任务说明 使用原生canvasAPI绘制 ...
- 使用Python为中秋节绘制一块美味的月饼
每逢佳节- 对于在外的游子,每逢佳节倍思亲.而对于996ICU的苦逼程序猿们,最期待的莫过于各种节假日能把自己丢在床上好好休息一下了.这几天各公司都陆续开始发中秋礼品了.朋友圈各种秀高颜值的月饼,所以 ...
- 升级xcode10.0, 终端运行 运行报错:Print: Entry, “:CFBundleIdentifier”, Does Not Exist
目录 问题原因 解决方法 终端解决 Print: Entry, ":CFBundleIdentifier", Does Not Exist 第一步: 去官网GitHub下载对应包就 ...
- Spring Data初步--整合Hibernate
Spring Data课程中的技术介绍 Hibernate: Hibernate 是一个开放源代码的对象关系映射框架,它对 JDBC 进行了非常轻量级的对象封装,它将 pojo 与数据库表建立映射关系 ...
- IO 文件夹的拷贝
package FileCopy; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import ja ...
- 2019CCPC秦皇岛 E题 Escape(网络流)
Escape Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 131072/131072 K (Java/Others)Total Su ...