• 读写分离子系统


    读写分离子系统 - 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科技,服务社会。
     
    分类: AOP架构可扩展
  • 相关阅读:
    组合模式
    过滤器模式
    桥接模式
    适配器模式
    原型模式
    建造者模式
    抽象工厂
    工厂方法
    静态工厂
    单例模式
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/3227181.html
Copyright © 2020-2023  润新知