读写分离子系统 - C# SQL分发子系统(ADO.NET,不支持EF)
这次介绍的这个框架只适用于中小项目,并且各个读写数据库结构是一致的情况,还要并且是写入数据库只有1台情况。
我们来看看这个子系统适用的场景:
我们来看这个子系统的配置文件:
<?xml version="1.0" encoding="utf-8" ?> <SQLDispatcher> <WritableDB>Server=.;Database=d1;User Id=sa;Password=111111;</WritableDB> //唯一的主数据库(写入DB) <ReadDBs> <DB>Server=.;Database=d2;User Id=sa;Password=111111;</DB> //这些是普通的对等的读数据库,只是做了些普通索引优化 <DB>Server=.;Database=d3;User Id=sa;Password=111111;</DB> //同上 <DB>Server=.;Database=d4;User Id=sa;Password=111111;</DB> //同上 </ReadDBs> <DedicatedReadDBs> <DedicatedRegion> <Region>Optimization_Sales</Region> //这个区域代表所列出来的DB是专门针对销售报表优化索引的数据库 <DB>Server=.;Database=d5;User Id=sa;Password=111111;</DB> <DB>Server=.;Database=d6;User Id=sa;Password=111111;</DB> </DedicatedRegion> <DedicatedRegion> <Region>Optimization_HR</Region> //这样的专门Region可以有多个区域 <DB>Server=.;Database=d7;User Id=sa;Password=111111;</DB> </DedicatedRegion> </DedicatedReadDBs> </SQLDispatcher>
上述配置文件的读取,略。
业务层中,可以做到这样的写法:
[AOPServiceEnabled()] //这句是为了和AOP代理挂钩 public class OrderQueryService : OrderQueryServiceInterface { IOrderQueryServiceDataProvider dp = new OrderQueryServiceSqlDataProvider(); public override QueryResult<QueryDto.OrderDto> QueryByFirstName(string firstName, PagingInfo pgInfo) { //这个函数没有加SQLDispatcher标记,系统会自己选择sql连接(写入sql:就那1个;读取sql:从ReadDBs中取模选中1个) QueryResult<QueryDto.OrderDto> lst=dp.QueryByFirstName(firstName, pgInfo); foreach (OrderDto o in lst.List) o.FirstName += DateTime.Now.ToString(); return lst; } [SQLDispatcher("Optimization_Sales")] //显式指定sql语句走 Optimization_Sales区域 public override QueryResult<QueryDto.OrderDto> QueryByEmail(string email) { QueryResult<QueryDto.OrderDto> lst = dp.QueryByEmail(email); return lst; } }
我们来看下UML:
SQLDispatcherContext用于保存当前函数的Region,这里保存的数据是瞬间的,随着函数的开始执行而有数据,随着函数的结束而被reset。
DBSelector是核心算法,用于根据配置文件算出不同的可选db,代码如下
public class DBSelector { public static DB SelectDB(string sql, string region) { bool redirect2WritableDB = false; sql = sql.Trim().TrimStart(' ').TrimStart(' '); if (sql.IndexOf("UPDATE", StringComparison.OrdinalIgnoreCase) >= 0) redirect2WritableDB = true; if (sql.IndexOf("DELETE", StringComparison.OrdinalIgnoreCase) >= 0) redirect2WritableDB = true; if (sql.IndexOf("INSERT", StringComparison.OrdinalIgnoreCase) >= 0) redirect2WritableDB = true; if (sql.IndexOf("--WRITE", StringComparison.OrdinalIgnoreCase) == 0) //强制sql方式进入写db操作 redirect2WritableDB = true; if (redirect2WritableDB) return Config.SQLDispatcherConfiguration.WritableDB; if (region == null || region.Length == 0) //from normal read dbs { int random = GenerateRandomNumber(); int dbIndex = random % Config.SQLDispatcherConfiguration.ReadDBs.Count; return Config.SQLDispatcherConfiguration.ReadDBs[dbIndex]; } DedicatedRegion r = Config.SQLDispatcherConfiguration.DedicatedRegions.Find(t => t.Region.Equals(region, StringComparison.OrdinalIgnoreCase)); if (r == null) throw new Exception("No such Dedicated Region Identifier."); { int random = GenerateRandomNumber(); int dbIndex = random % r.DBs.Count; return r.DBs[dbIndex]; } } private static int GenerateRandomNumber() { Random Random1 = new Random(); //产生0到1000的随机数 int i1 = Random1.Next(0, 1001); return i1; } }
SqlHelperCoordinator类只是简单的根据DBSelector算出的结果调度真正的SqlHelper来执行:
public sealed class SqlHelperCoordinator { public static int ExecuteNonQuery(CommandType commandType, string commandText, params SqlParameter[] commandParameters) { string region = SQLDispatcherContext.GetCurrentContext().Region; DB db=DBSelector.SelectDB(commandText, region); return SqlHelper.ExecuteNonQuery(db.ConnectionString, commandType, commandText, commandParameters); } public static SqlDataReader ExecuteReader(CommandType commandType, string commandText, params SqlParameter[] commandParameters) { string region = SQLDispatcherContext.GetCurrentContext().Region; DB db = DBSelector.SelectDB(commandText, region); return SqlHelper.ExecuteReader(db.ConnectionString, commandType, commandText, commandParameters); } }
Console测试代码(记得打开Sql profile检测sql哦):
static void Main(string[] args) { InstancePoolResolver.Register<OrderQueryServiceInterface, OrderQueryService>(); using (OrderQueryServiceInterface srv = InstancePoolResolver.Resolve<OrderQueryServiceInterface>()) { while (true) { //Thread.Sleep(1000); Console.ReadKey(); QueryResult<Core.QueryService.QueryDto.OrderDto> lst=srv.QueryByFirstName("aaron", new CoreFramework.QueryService.PagingInfo() { PageIndex = 0, PageSize = 10, OrderByColumn = "FirstName", IsAscendingSort = true }); lst.List.ForEach(t=>Console.WriteLine(t.FirstName)); srv.QueryByEmail("aaron"); } } }
运行2次(关闭后再运行,因为缓存还没有好,bug)
就会看到:
被查询的数据库名正好落在xml配置文件的范围
自省推动进步,视野决定未来。
心怀远大理想。
为了家庭幸福而努力。
用A2D科技,服务社会。
心怀远大理想。
为了家庭幸福而努力。
用A2D科技,服务社会。