回到目录,完整代码请查看https://github.com/cjw0511/NDF.Infrastructure)中的目录:
     src\ NDF.Data.EntityFramework\MasterSlaves
 
    上一回中(http://www.cnblogs.com/cjw0511/p/4398267.html),我们简单讲述了基于 EF 来实现数据库读写分离的原理。当然,这只是一个 demo 级别的简单实现,实际上,在我们工作环境中,碰到的情况远比这复杂多了,例如数据库连接的配置是通过 config 文件来存储、在进行数据库操作时还需要附带很多事务操作功能等等。今天我们就来聊聊如何处理这些问题。
    
首先,我们来解决数据库连接字符串存储与配置文件的问题
     代码如下:

  public class DbMasterSlaveCommandInterceptor : DbCommandInterceptor
{
private Lazy<string> masterConnectionString = new Lazy<string>(() => ConfigurationManager.AppSettings["masterConnectionString"]);
private Lazy<string> slaveConnectionString = new Lazy<string>(() => ConfigurationManager.AppSettings["slaveConnectionString"]); public string MasterConnectionString
{
get { return this.masterConnectionString.Value; }
} public string SlaveConnectionString
{
get { return this.slaveConnectionString.Value; }
} public override void ReaderExecuting(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
{
this.UpdateConnectionStringIfNeed(interceptionContext, this.SlaveConnectionString);
} public override void ScalarExecuting(DbCommand command, DbCommandInterceptionContext<object> interceptionContext)
{
this.UpdateConnectionStringIfNeed(interceptionContext, this.SlaveConnectionString);
} public override void NonQueryExecuting(DbCommand command, DbCommandInterceptionContext<int> interceptionContext)
{
this.UpdateConnectionStringIfNeed(interceptionContext, this.MasterConnectionString);
} private void UpdateConnectionStringIfNeed(DbInterceptionContext interceptionContext, string connectionString)
{
foreach (var context in interceptionContext.DbContexts)
{
this.UpdateConnectionStringIfNeed(context.Database.Connection, connectionString);
}
} /// <summary>
/// 此处改进了对连接字符串的修改判断机制,确认只在 <paramref name="conn"/> 所使用的连接字符串不等效于 <paramref name="connectionString"/> 的情况下才需要修改。
/// </summary>
/// <param name="conn"></param>
/// <param name="connectionString"></param>
private void UpdateConnectionStringIfNeed(DbConnection conn, string connectionString)
{
if (this.ConnectionStringCompare(conn, connectionString))
{
ConnectionState state = conn.State;
if (state == ConnectionState.Open)
conn.Close(); conn.ConnectionString = connectionString; if (state == ConnectionState.Open)
conn.Open();
}
} private bool ConnectionStringCompare(DbConnection conn, string connectionString)
{
DbProviderFactory factory = DbProviderFactories.GetFactory(conn); DbConnectionStringBuilder a = factory.CreateConnectionStringBuilder();
a.ConnectionString = conn.ConnectionString; DbConnectionStringBuilder b = factory.CreateConnectionStringBuilder();
b.ConnectionString = connectionString; return a.EquivalentTo(b);
}
}

再者,我们来聊聊数据库操作中的事务处理。

    我们都知道,数据库操作中的事务处理重要包括两大类:
    1、普通数据库操作事务处理,该类型由 DbTransaction 事务基类来控制;
    2、分布式事务,这类操作主要由组件 System.Transactions 来控制,最常用的类型包括 Transaction 和 TransactionScope。
    
    具体涉及到普通数据库事务和分布式事务的意义和区别、普通事务如何会提升为分布式事务等知识点,这里就不赘述了,有兴趣的同学可以另行补课。
    这里需要说明的是,在数据库的事务操作中,很多 dbms 是不支持同一个事务操作不同的数据库或服务器的。另外某些 dbms 支持同一个事务操作多个数据库或服务器(自动提升为分布式事务),但是需要 msdtc 的支持。
    
    所以在这里,我改进的方案是,凡是所有的事务操作,不管是普通数据库事务,还是分布式事务,都“禁用”读写分离,即将所有的在事务内的数据库操作(不管是读还是写,虽然这一定程度上不符合“完全的读写分离”的本意,但是解决了数据库事务兼容性的问题,而且大多数项目开发中,包含事务的操作不占多数),都指向 Master 服务器。实际上基于我们前面对数据库服务器连接字符串的封装,要实现这一点,只需要改动少量代码,如下:
 public class DbMasterSlaveCommandInterceptor : DbCommandInterceptor
{
private Lazy<string> masterConnectionString = new Lazy<string>(() => ConfigurationManager.AppSettings["masterConnectionString"]);
private Lazy<string> slaveConnectionString = new Lazy<string>(() => ConfigurationManager.AppSettings["slaveConnectionString"]); public string MasterConnectionString
{
get { return this.masterConnectionString.Value; }
} public string SlaveConnectionString
{
get { return this.slaveConnectionString.Value; }
} public override void ReaderExecuting(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
{
this.UpdateToSlave(interceptionContext);
} public override void ScalarExecuting(DbCommand command, DbCommandInterceptionContext<object> interceptionContext)
{
this.UpdateToSlave(interceptionContext);
} public override void NonQueryExecuting(DbCommand command, DbCommandInterceptionContext<int> interceptionContext)
{
this.UpdateToMaster(interceptionContext);
} private void UpdateToMaster(DbInterceptionContext interceptionContext)
{
foreach (var context in interceptionContext.DbContexts)
{
this.UpdateConnectionStringIfNeed(context.Database.Connection, this.MasterConnectionString);
}
} private void UpdateToSlave(DbInterceptionContext interceptionContext)
{
// 判断当前会话是否处于分布式事务中
bool isDistributedTran = Transaction.Current != null && Transaction.Current.TransactionInformation.Status != TransactionStatus.Committed;
foreach (var context in interceptionContext.DbContexts)
{
// 判断该 context 是否处于普通数据库事务中
bool isDbTran = context.Database.CurrentTransaction != null; // 如果处于分布式事务或普通事务中,则“禁用”读写分离,处于事务中的所有读写操作都指向 Master
string connectionString = isDistributedTran || isDbTran ? this.MasterConnectionString : this.SlaveConnectionString; this.UpdateConnectionStringIfNeed(context.Database.Connection, connectionString);
}
} /// <summary>
/// 此处改进了对连接字符串的修改判断机制,确认只在 <paramref name="conn"/> 所使用的连接字符串不等效于 <paramref name="connectionString"/> 的情况下才需要修改。
/// <para>同时,在必要的情况下才会连接进行 Open 和 Close 操作以及修改 ConnectionString 处理,减少了性能的消耗。</para>
/// </summary>
/// <param name="conn"></param>
/// <param name="connectionString"></param>
private void UpdateConnectionStringIfNeed(DbConnection conn, string connectionString)
{
if (this.ConnectionStringCompare(conn, connectionString))
{
this.UpdateConnectionString(conn, connectionString);
}
} private void UpdateConnectionString(DbConnection conn, string connectionString)
{
ConnectionState state = conn.State;
if (state == ConnectionState.Open)
conn.Close(); conn.ConnectionString = connectionString; if (state == ConnectionState.Open)
conn.Open();
} private bool ConnectionStringCompare(DbConnection conn, string connectionString)
{
DbProviderFactory factory = DbProviderFactories.GetFactory(conn); DbConnectionStringBuilder a = factory.CreateConnectionStringBuilder();
a.ConnectionString = conn.ConnectionString; DbConnectionStringBuilder b = factory.CreateConnectionStringBuilder();
b.ConnectionString = connectionString; return a.EquivalentTo(b);
}
}
    关于上面的代码,需要说明的一点是,因为要获取 EF DbContext 的普通数据库事务状态,必须得拿到 DbContext.Database.CurrentTransaction 属性,所以将 UpdateConnectionString 方法拆分成 UpdateToMaster 和 UpdateToSlave 了。

基于 EntityFramework 的数据库主从读写分离架构(2)- 改进配置和添加事务支持的更多相关文章

  1. 基于 EntityFramework 的数据库主从读写分离架构 - 目录

    基于 EntityFramework 的数据库主从读写分离架构       回到目录,完整代码请查看(https://github.com/cjw0511/NDF.Infrastructure)中的目 ...

  2. 基于 EntityFramework 的数据库主从读写分离架构(1) - 原理概述和基本功能实现

        回到目录,完整代码请查看(https://github.com/cjw0511/NDF.Infrastructure)中的目录:      src\ NDF.Data.EntityFramew ...

  3. 基于 EntityFramework 的数据库主从读写分离服务插件

    基于 EntityFramework 的数据库主从读写分离服务插件 1. 版本信息和源码 1.1 版本信息 v1.01 beta(2015-04-07),基于 EF 6.1 开发,支持 EF 6.1 ...

  4. 基于 EntityFramework 的数据库主从读写分离

    现在刚开始来研究EntityFramwork,起初是在vs2012中通过工具来创建EF ,但是对我这种不熟悉菜鸟来说 有很多业务用EF做出来还是有点难度的,今天来手动搭建一个EF框架,大神勿喷

  5. 基于Amoba实现mysql主从读写分离

    一.Amoeba简介           Amoeba是一个以MySQL为底层数据存储,并对应用提供MySQL协议接口的proxy.它集中地响应应用的请求,依据用户事先设置的规则,将SQL请求发送到特 ...

  6. Amoeba搞定mysql主从读写分离

    前言:一直想找一个工具,能很好的实现mysql主从的读写分离架构,曾经试用过mysql-proxy发现lua用起来很不爽,尤其是不懂lua脚本,突然发现了Amoeba这个项目,试用了下,感觉还不错,写 ...

  7. MySQL搭建主从数据库 实现读写分离

    首先声明,实际生产中,网站为了提高用户体验,性能等,将数据库实现读写分离是有必要的,我们让主数据库去写入数据,然后当用户查询的时候,然后在从数据库读取数据,故能减轻数据库的压力,实现良好的用户体验! ...

  8. Amoeba实现mysql主从读写分离

    Amoeba实现mysql主从读写分离 这段在网上看了下关于amoeba的文章,总体感觉好像要比mysql-proxy好的多,也参考了不少的资料,此文章可能与其他文章作者会有雷同的地方,请谅解,但是此 ...

  9. Mycat - 实现数据库的读写分离与高可用

    前言 开心一刻 上语文课,不小心睡着了,坐在边上的同桌突然叫醒了我,并小声说道:“读课文第三段”.我立马起身大声读了起来.正在黑板写字的老师吓了一跳,老师郁闷的看着我,问道:“同学有什么问题吗?”,我 ...

随机推荐

  1. linux操作系统安全防护

    Linux系统攻防对抗实践 一.实践内容 在Metasploit渗透攻击框架软件中寻找一个针对Linux系统服务的渗透攻击模块,在网络安全攻防实验环境中部署有漏洞的环境(如渗透利用第三方网络服务,需要 ...

  2. MySQL免安装版安装配置、修改密码

    一:MySQL的下载安装 1.1 下载 我下载的是 ZIP Archive 版的,win7 64位的机器支持使用,而且相对而言,简单.干净. 首先,进入MySQL的官方网址,依次点击Downloads ...

  3. remoting与socket、web service的比较及实例

    remoting基础 一种分布式处理方式,可以说是DCOM的一种升级 跨过应用程序域,与另外的应用程序域进行通信,即穿越边界 在remoting中是通过通道(channel)来实现两个应用程序域之间对 ...

  4. Linux 之 NTP 服务 服务器

    我们选择第三台机器( mysql02 )为 NTF 服务器,其他机器和这台机器进行同步. 1. 检查 ntp 服务是否已经安装[root@mysql02 ~]# sudo rpm -qa | grep ...

  5. bzoj 3295 (洛谷3157、3193) [Cqoi2011]动态逆序对——树套树 / CDQ分治

    题目:https://www.lydsy.com/JudgeOnline/problem.php?id=3295 题目--洛谷3157:https://www.luogu.org/problemnew ...

  6. 2016女生赛 HDU 5710 Digit-Sum(数学,思维题)

    Digit-Sum Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65535/32768 K (Java/Others)Total S ...

  7. PHP命名空间带来的干扰

    有时候,不想受命名空间约束,就可以整一个全局类. protected function sendDayuSms($tel,$code,$template_type,$product = "[ ...

  8. cocoa 线程操作

    在Cocoa 中创建线程使用NSThread类的detachNewThreadSelector: toTarget:withObject:方法 NSPort *port1 = [NSPort port ...

  9. SQL SERVER常用的统计用法

    --查询各个状态下总数SET statistics time on Go SELECT END ) AS Q1, END ) AS Q2, END ) AS Q3, END ) AS Q4, END ...

  10. 初识 Julia

    Ubuntu 下安装 Julia 环境 sch01ar@ubuntu:~$ sudo apt install julia 安装完成后打开 Julia 的交互式会话 sch01ar@ubuntu:~$ ...