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

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

src\ NDF.Data.EntityFramework\MasterSlaves

上一回中(http://www.cnblogs.com/cjw0511/p/4398267.html),我们简单讲述了基于 EF 来实现数据库读写分离的原理。当然,这只是一个 demo 级别的简单实现,实际上,在我们工作环境中,碰到的情况远比这复杂多了,例如数据库连接的配置是通过 config 文件来存储、在进行数据库操作时还需要附带很多事务操作功能等等。今天我们就来聊聊如何处理这些问题。

首先,我们来解决数据库连接字符串存储与配置文件的问题

代码如下:

 1  public class DbMasterSlaveCommandInterceptor : DbCommandInterceptor
 2     {
 3         private Lazy<string> masterConnectionString = new Lazy<string>(() => ConfigurationManager.AppSettings["masterConnectionString"]);
 4         private Lazy<string> slaveConnectionString = new Lazy<string>(() => ConfigurationManager.AppSettings["slaveConnectionString"]);
 5
 6         public string MasterConnectionString
 7         {
 8             get { return this.masterConnectionString.Value; }
 9         }
10
11         public string SlaveConnectionString
12         {
13             get { return this.slaveConnectionString.Value; }
14         }
15
16
17         public override void ReaderExecuting(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
18         {
19             this.UpdateConnectionStringIfNeed(interceptionContext, this.SlaveConnectionString);
20         }
21
22         public override void ScalarExecuting(DbCommand command, DbCommandInterceptionContext<object> interceptionContext)
23         {
24             this.UpdateConnectionStringIfNeed(interceptionContext, this.SlaveConnectionString);
25         }
26
27         public override void NonQueryExecuting(DbCommand command, DbCommandInterceptionContext<int> interceptionContext)
28         {
29             this.UpdateConnectionStringIfNeed(interceptionContext, this.MasterConnectionString);
30         }
31
32
33         private void UpdateConnectionStringIfNeed(DbInterceptionContext interceptionContext, string connectionString)
34         {
35             foreach (var context in interceptionContext.DbContexts)
36             {
37                 this.UpdateConnectionStringIfNeed(context.Database.Connection, connectionString);
38             }
39         }
40
41         /// <summary>
42         /// 此处改进了对连接字符串的修改判断机制,确认只在 <paramref name="conn"/> 所使用的连接字符串不等效于 <paramref name="connectionString"/> 的情况下才需要修改。
43         /// </summary>
44         /// <param name="conn"></param>
45         /// <param name="connectionString"></param>
46         private void UpdateConnectionStringIfNeed(DbConnection conn, string connectionString)
47         {
48             if (this.ConnectionStringCompare(conn, connectionString))
49             {
50                 ConnectionState state = conn.State;
51                 if (state == ConnectionState.Open)
52                     conn.Close();
53
54                 conn.ConnectionString = connectionString;
55
56                 if (state == ConnectionState.Open)
57                     conn.Open();
58             }
59         }
60
61         private bool ConnectionStringCompare(DbConnection conn, string connectionString)
62         {
63             DbProviderFactory factory = DbProviderFactories.GetFactory(conn);
64
65             DbConnectionStringBuilder a = factory.CreateConnectionStringBuilder();
66             a.ConnectionString = conn.ConnectionString;
67
68             DbConnectionStringBuilder b = factory.CreateConnectionStringBuilder();
69             b.ConnectionString = connectionString;
70
71             return a.EquivalentTo(b);
72         }
73     }

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

我们都知道,数据库操作中的事务处理重要包括两大类:

1、普通数据库操作事务处理,该类型由 DbTransaction 事务基类来控制;

2、分布式事务,这类操作主要由组件 System.Transactions 来控制,最常用的类型包括 Transaction 和 TransactionScope。

具体涉及到普通数据库事务和分布式事务的意义和区别、普通事务如何会提升为分布式事务等知识点,这里就不赘述了,有兴趣的同学可以另行补课。

这里需要说明的是,在数据库的事务操作中,很多 dbms 是不支持同一个事务操作不同的数据库或服务器的。另外某些 dbms 支持同一个事务操作多个数据库或服务器(自动提升为分布式事务),但是需要 msdtc 的支持。

所以在这里,我改进的方案是,凡是所有的事务操作,不管是普通数据库事务,还是分布式事务,都“禁用”读写分离,即将所有的在事务内的数据库操作(不管是读还是写,虽然这一定程度上不符合“完全的读写分离”的本意,但是解决了数据库事务兼容性的问题,而且大多数项目开发中,包含事务的操作不占多数),都指向 Master 服务器。实际上基于我们前面对数据库服务器连接字符串的封装,要实现这一点,只需要改动少量代码,如下:

 1 public class DbMasterSlaveCommandInterceptor : DbCommandInterceptor
 2     {
 3         private Lazy<string> masterConnectionString = new Lazy<string>(() => ConfigurationManager.AppSettings["masterConnectionString"]);
 4         private Lazy<string> slaveConnectionString = new Lazy<string>(() => ConfigurationManager.AppSettings["slaveConnectionString"]);
 5
 6         public string MasterConnectionString
 7         {
 8             get { return this.masterConnectionString.Value; }
 9         }
10
11         public string SlaveConnectionString
12         {
13             get { return this.slaveConnectionString.Value; }
14         }
15
16
17         public override void ReaderExecuting(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
18         {
19             this.UpdateToSlave(interceptionContext);
20         }
21
22         public override void ScalarExecuting(DbCommand command, DbCommandInterceptionContext<object> interceptionContext)
23         {
24             this.UpdateToSlave(interceptionContext);
25         }
26
27         public override void NonQueryExecuting(DbCommand command, DbCommandInterceptionContext<int> interceptionContext)
28         {
29             this.UpdateToMaster(interceptionContext);
30         }
31
32
33         private void UpdateToMaster(DbInterceptionContext interceptionContext)
34         {
35             foreach (var context in interceptionContext.DbContexts)
36             {
37                 this.UpdateConnectionStringIfNeed(context.Database.Connection, this.MasterConnectionString);
38             }
39         }
40
41         private void UpdateToSlave(DbInterceptionContext interceptionContext)
42         {
43             // 判断当前会话是否处于分布式事务中
44             bool isDistributedTran = Transaction.Current != null && Transaction.Current.TransactionInformation.Status != TransactionStatus.Committed;
45             foreach (var context in interceptionContext.DbContexts)
46             {
47                 // 判断该 context 是否处于普通数据库事务中
48                 bool isDbTran = context.Database.CurrentTransaction != null;
49
50                 // 如果处于分布式事务或普通事务中,则“禁用”读写分离,处于事务中的所有读写操作都指向 Master
51                 string connectionString = isDistributedTran || isDbTran ? this.MasterConnectionString : this.SlaveConnectionString;
52
53                 this.UpdateConnectionStringIfNeed(context.Database.Connection, connectionString);
54             }
55         }
56
57
58         /// <summary>
59         /// 此处改进了对连接字符串的修改判断机制,确认只在 <paramref name="conn"/> 所使用的连接字符串不等效于 <paramref name="connectionString"/> 的情况下才需要修改。
60         /// <para>同时,在必要的情况下才会连接进行 Open 和 Close 操作以及修改 ConnectionString 处理,减少了性能的消耗。</para>
61         /// </summary>
62         /// <param name="conn"></param>
63         /// <param name="connectionString"></param>
64         private void UpdateConnectionStringIfNeed(DbConnection conn, string connectionString)
65         {
66             if (this.ConnectionStringCompare(conn, connectionString))
67             {
68                 this.UpdateConnectionString(conn, connectionString);
69             }
70         }
71
72         private void UpdateConnectionString(DbConnection conn, string connectionString)
73         {
74             ConnectionState state = conn.State;
75             if (state == ConnectionState.Open)
76                 conn.Close();
77
78             conn.ConnectionString = connectionString;
79
80             if (state == ConnectionState.Open)
81                 conn.Open();
82         }
83
84         private bool ConnectionStringCompare(DbConnection conn, string connectionString)
85         {
86             DbProviderFactory factory = DbProviderFactories.GetFactory(conn);
87
88             DbConnectionStringBuilder a = factory.CreateConnectionStringBuilder();
89             a.ConnectionString = conn.ConnectionString;
90
91             DbConnectionStringBuilder b = factory.CreateConnectionStringBuilder();
92             b.ConnectionString = connectionString;
93
94             return a.EquivalentTo(b);
95         }
96     }

关于上面的代码,需要说明的一点是,因为要获取 EF DbContext 的普通数据库事务状态,必须得拿到 DbContext.Database.CurrentTransaction 属性,所以将 UpdateConnectionString 方法拆分成 UpdateToMaster 和 UpdateToSlave 了。

时间: 2024-10-09 23:03:36

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

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

基于 EntityFramework 的数据库主从读写分离架构 回到目录,完整代码请查看(https://github.com/cjw0511/NDF.Infrastructure)中的目录: src\ NDF.Data.EntityFramework\MasterSlaves 基于 EntityFramework 的数据库主从读写分离架构 - 需求/功能概述 基于 EntityFramework 的数据库主从读写分离架构(1)- 原理概述和基本功能实现 基于 EntityFramework 的

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

http://www.midifan.com/moduleuser-index-435678.htmhttp://www.midifan.com/moduleuser-index-435633.htmhttp://www.midifan.com/moduleuser-index-435673.htmhttp://www.midifan.com/moduleuser-index-435744.htmhttp://www.midifan.com/moduleuser-index-435660.htm

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

基于 EntityFramework 的数据库主从读写分离服务插件 1.       版本信息和源码 1.1 版本信息 v1.0 beta(2015-04-02),基于 EF 6.1 开发,支持 EF 6.1 之后的所有 EF6 版本. 1.2 开放源码地址 https://github.com/cjw0511/NDF.Infrastructure 关于该 EF 数据库主从读写分离服务核心源码位于文件夹: src\ NDF.Data.EntityFramework\MasterSlaves 文件

基于Mycat的MySQL主从读写分离配置详解与示例

1.mycat二进制包安装 tar -zxvf Mycat-server-1.6.5-release-20180122220033-linux.tar.gzcd mycatmv mycat /opt/ useradd mycatchown -R mycat:mycat mycat 2.mysql操作搭建主库环境省略...... 创建数据库CREATE DATABASE `integration01` DEFAULT CHARACTER SET utf8 ; 创建物理表 CREATE TABLE

Amoeba实现mysql主从读写分离

架设amoeba,实现mysql主从读写分 安装amoeba前需要先安装jdk,因为amoeba是JAVA编写的,所以需要JDK环境的支持,至于版本需要在JAVA1.5以后,mysql数据库需要在4.1以后的版本. 以下是我的实验环境. System:    CentOS 6.5 Master mysql:192.168.88.133 Slave mysql:192.168.88.135 Amoeba server:   192.168.88.131 安装mysql及配置mysql主从这里省略,

Linux下mysql基于MyCat实现主从复制和读写分离

1.1 MyCat介绍及应用场景MyCat介绍MyCat是一个开源的分布式数据库系统,是一个实现了MySQL协议的服务器,前端用户可以把它看作是一个数据库代理,用MySQL客户端工具和命令行访问,而其后端可以用MySQL原生协议与多个MySQL服务器通信,也可以用JDBC协议与大多数主流数据库服务器通信,其核心功能是分表分库,即将一个大表水平分割为N个小表,存储在后端MySQL服务器里或者其他数据库里.MyCat发展到目前的版本,已经不是一个单纯的MySQL代理了,它的后端可以支持MySQL.S

MySQL数据库之读写分离

一.概述: MySQL数据库主从结构配置以后,正常情况下数据库的所有读写操作全部都在主数据库上面,从数据库仅仅作为数据备份使用,显然无法有效的使用服务器资源,那么实现读写分离的需求就不可避免. 二.拓扑图说明: 如上图所示,本文要实现的是读MySQL数据库的写入操作(增删改)等在Master服务器(192.168.4.10)上面实现,而对MySQL数据库的读取操作(查询)等在Slave服务器(192.168.4.20)上面完成. 如果在程序员编程时创建两个数据库连接Connection,在程序中

linux下mysql基于mycat做主从复制和读写分离之基础篇

Linux下mysql基于mycat实现主从复制和读写分离1.基础设施 两台虚拟机:172.20.79.232(主) 172.20.79.233(从) 1.1软件设施 mysql5.6.39 , mycat1.6-RELEASE jdk1.7及其以上版本2.实现步骤一(mycat实现读写分离) 1.首先在两台服务器安装mysql 1.下载mysql的repo源 $ wget http://repo.mysql.com/mysql-community-release-el7-5.noarch.rp

数据库_读写分离-多实例应用

1.数据读写分离介绍及搭建案例; 2.多实例服务. 一,数据读写分离介绍 1.概念:把客户端访问的查询请求和写请求,分别给不同的数据库服务器处理. 2.优点: 减轻主服务器的工作压力; 提高从服务器的硬件利用率 3.实现方式,有客户端指定和服务端指定两种. 客户端指定:程序写代码实现.例,插入数据的时候,连接主库;查询数据的时候,连接从库. 服务端指定:在服务器上部署数据读写分离的服务. 4.数据分离的软件:maxscal,mysql-proxy,mycat.这些软件也叫中间件. 5.原理: 由