读写分离子系统

这次介绍的这个框架只适用于中小项目,并且各个读写数据库结构是一致的情况,还要并且是写入数据库只有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配置文件的范围

代码下载

原文地址:https://www.cnblogs.com/aarond/p/SQLDispatcher.html