文章出處

回到目錄

上一講中基本實現了對數據庫的讀寫分離,而在選擇只讀數據庫上只是隨機選擇,并沒有去檢測數據庫服務器是否有效,如服務器掛了,SQL服務停了,端口被封了等等,而本講主要對以上功能進行一個實現,并對配置文件也進行了一些優化,讓它更好的支持多個數據庫服務器,分別配置各個的賬號和密碼及數據庫服務端口等等,接下來,就來看一下主要的代碼吧。

一 配置文件

    <!-- ef實現對sql讀寫分離的配置,sqlserver端采用發布與訂閱實現 -->
    <add key="readDb" value="
         192.168.2.71|1433|background_read1|sa|zzl123,
         192.168.2.71|1433|TestWrite_Read_Zzl|sa|zzl123,
         192.168.2.29|1433|TestWrite_Read_Zzl|sa|1"
         />
    <!-- 只讀服務器的sql連接串配置模版-->
    <add key ="readDbConnectioin" value="data source={0};initial catalog={1};persist security info=True;user id={2};password={3};multipleactiveresultsets=True;application name=EntityFramework"/>

二 數據庫配置實體類

 /// <summary>
    /// 只讀數據庫配置實體
    /// </summary>
    public class ReadDbConfig
    {
        public ReadDbConfig()
        {
            Port = 1433;
            UserId = "sa";
        }
        public string Ip { get; set; }
        public int Port { get; set; }
        public string DbName { get; set; }
        public string UserId { get; set; }
        public string Password { get; set; }
    }

三 對SQL攔截器進行優化,添加了TCP的心跳檢測

lock (lockObj)
            {
                if (readConnList != null && readConnList.Any())
                {
                    foreach (var item in readConnList)
                    {
                        //心跳測試,將死掉的服務器IP從列表中移除
                        var client = new TcpClient();
                        try
                        {
                            client.Connect(new IPEndPoint(IPAddress.Parse(item.Ip), item.Port));
                        }
                        catch (SocketException)
                        {
                            //異常,沒有連接上
                            readConnList.Remove(item);
                        }
                        if (!client.Connected)
                        {
                            readConnList.Remove(item);
                        }
                    }
                }
            }

四 對于數據庫庫端還是這前通過發布和訂閱實現的,需要注意的是,這些功能需要使用“機器名”進行鏈接,使用ip和域名都是無效的

五 下面貢獻一下完成的攔截器代碼

    /// <summary>
    /// SQL命令攔截器
    /// 主要實現EF的讀寫分離
    /// </summary>
    public class SqlCommandInterceptor : DbCommandInterceptor
    {
        static SqlCommandInterceptor()
        {
            InitConfig();

            initSysTimer.Enabled = true;
            initSysTimer.Elapsed += initSysTimer_Elapsed;
            initSysTimer.Start();

            sysTimer.Enabled = true;
            sysTimer.Elapsed += sysTimer_Elapsed;
            sysTimer.Start();
        }

        private static object lockObj = new object();
        /// <summary>
        /// 定期找沒有在線的數據庫服務器
        /// </summary>
        private static Timer sysTimer = new Timer(6);
        /// <summary>
        /// 系統配置文件輪訓讀時間間隔
        /// </summary>
        private static Timer initSysTimer = new Timer(60000 * 10);
        /// <summary>
        /// 讀庫,從庫集群,寫庫不用設置走默認的EF框架
        /// </summary>
        private static List<ReadDbConfig> readConnList;
        /// <summary>
        /// 配置初始化
        /// </summary>
        private static void InitConfig()
        {
            lock (lockObj)
            {
                var temp = new List<ReadDbConfig>();
                var str = System.Configuration.ConfigurationManager.AppSettings["readDb"] ?? string.Empty;
                var readList = str.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
                if (readList != null && readList.Any())
                {
                    foreach (var item in readList)
                    {
                        var configArr = item.Split(new char[] { '|' }, StringSplitOptions.RemoveEmptyEntries);
                        temp.Add(new ReadDbConfig
                        {
                            Ip = configArr[0],
                            Port = int.Parse(configArr[1]),
                            DbName = configArr[2],
                            UserId = configArr[3],
                            Password = configArr[4],
                        });
                    }
                }
                readConnList = temp;
            }
        }
        #region Private Methods
        private static void initSysTimer_Elapsed(object sender, ElapsedEventArgs e)
        {
            InitConfig();
        }
        /// <summary>
        /// 輪詢服務
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private static void sysTimer_Elapsed(object sender, ElapsedEventArgs e)
        {
            lock (lockObj)
            {
                if (readConnList != null && readConnList.Any())
                {
                    foreach (var item in readConnList)
                    {
                        //心跳測試,將死掉的服務器IP從列表中移除
                        var client = new TcpClient();
                        try
                        {
                            client.Connect(new IPEndPoint(IPAddress.Parse(item.Ip), item.Port));
                        }
                        catch (SocketException)
                        {
                            //異常,沒有連接上
                            readConnList.Remove(item);
                        }
                        if (!client.Connected)
                        {
                            readConnList.Remove(item);
                        }
                    }
                }
            }
        }
        /// <summary>
        /// 處理讀庫字符串
        /// </summary>
        /// <returns></returns>
        private string GetReadConn()
        {
            if (readConnList != null && readConnList.Any())
            {
                var resultConn = readConnList[Convert.ToInt32(Math.Floor((double)new Random().Next(0, readConnList.Count)))];
                return string.Format(System.Configuration.ConfigurationManager.AppSettings["readDbConnectioin"]
                    , resultConn.Ip
                    , resultConn.DbName
                    , resultConn.UserId
                    , resultConn.Password);
            }
            return string.Empty;
        }
        /// <summary>
        /// 只讀庫的選擇,加工command對象
        /// </summary>
        /// <param name="command"></param>
        private void ReadDbSelect(DbCommand command)
        {
            if (!string.IsNullOrWhiteSpace(GetReadConn()))//如果配置了讀寫分離,就去實現
            {
                if (!command.CommandText.StartsWith("insert", StringComparison.InvariantCultureIgnoreCase))
                {
                    command.Connection.Close();
                    command.Connection.ConnectionString = GetReadConn();
                    command.Connection.Open();
                }
            }
        }
        #endregion

        #region Override Methods
        /// <summary>
        /// Linq to Entity生成的update,delete
        /// </summary>
        /// <param name="command"></param>
        /// <param name="interceptionContext"></param>
        public override void NonQueryExecuting(DbCommand command, DbCommandInterceptionContext<int> interceptionContext)
        {
            base.NonQueryExecuting(command, interceptionContext);//update,delete等寫操作直接走主庫
        }
        /// <summary>
        /// 執行sql語句,并返回第一行第一列,沒有找到返回null,如果數據庫中值為null,則返回 DBNull.Value
        /// </summary>
        /// <param name="command"></param>
        /// <param name="interceptionContext"></param>
        public override void ScalarExecuting(DbCommand command, DbCommandInterceptionContext<object> interceptionContext)
        {
            ReadDbSelect(command);
            base.ScalarExecuting(command, interceptionContext);
        }
        /// <summary>
        /// Linq to Entity生成的select,insert
        /// 發送到sqlserver之前觸發
        /// warning:在select語句中DbCommand.Transaction為null,而ef會為每個insert添加一個DbCommand.Transaction進行包裹
        /// </summary>
        /// <param name="command"></param>
        /// <param name="interceptionContext"></param>
        public override void ReaderExecuting(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
        {
            ReadDbSelect(command);
            base.ReaderExecuted(command, interceptionContext);
        }
        /// <summary>
        /// 發送到sqlserver之后觸發
        /// </summary>
        /// <param name="command"></param>
        /// <param name="interceptionContext"></param>
        public override void ReaderExecuted(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
        {
            base.ReaderExecuted(command, interceptionContext);
        }

        #endregion
    }
View Code

回到目錄


文章列表




Avast logo

Avast 防毒軟體已檢查此封電子郵件的病毒。
www.avast.com


arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()