关于Dapper实现读写分离的个人思考
概念相關
????為了確保多線上環(huán)境數(shù)據庫的穩(wěn)定性和可用性,大部分情況下都使用了雙機熱備的技術。一般是一個主庫+一個從庫或者多個從庫的結構,從庫的數(shù)據來自于主庫的同步。在此基礎上我們可以通過數(shù)據庫反向代理工具或者使用程序的方式實現(xiàn)讀寫分離,即主庫接受事務性操作比如刪除、修改、新增等操作,從庫接受讀操作。筆者自認為讀寫分離解決的痛點是,數(shù)據庫讀寫負載非常高的情況下,單點數(shù)據庫存在讀寫沖突,從而導致數(shù)據庫壓力過大,出現(xiàn)讀寫操作緩慢甚至出現(xiàn)死鎖或者拒絕服務的情況。它適用與讀大于寫,并可以容忍一段時間內不一致的情況,因為主從同步存在一定的延遲,大致的實現(xiàn)架構圖如下(圖片來自于網絡)。
????
????雖然我們可以通過數(shù)據庫代理實現(xiàn)讀寫分離,比如mycat,這類方案的優(yōu)勢就是對程序本身沒有入侵,通過代理本身來攔截sql語句分發(fā)到具體數(shù)據。甚至是更好的解決方案NewSQL去解決,比如TiDB。但是還是那個原則,無論使用數(shù)據庫代理或者NewSQL的情況都是比較重型的解決方案,會增加服務節(jié)點和運維成本,有時候還沒到使用這些終極解決方案的地步,這時候我們會在程序中處理讀寫分離,所以有個好的思路去在程序中解決讀寫分離也尤為重要。
基本結構
接下來我們新建三個類,當然這個并不固定,可以根據自己的情況新建類。首先我們新建一個ConnectionStringConsts用來存放連接字符串常量,也就是用來存放讀取自配置文件或者配置中心的字符串,這里我直接寫死,當然你也可以存放多個連接字符串,大致實現(xiàn)如下。
public class ConnectionStringConsts {/// <summary>/// 主庫連接字符串/// </summary>public static readonly string MasterConnectionString = "server=db.master.com;Database=crm_db;UID=root;PWD=1";/// <summary>/// 從庫連接字符串/// </summary>public static readonly string SlaveConnectionString = "server=db.slave.com;Database=crm_db;UID=root;PWD=1"; }然后新建存儲數(shù)據庫連接字符串主從映射關系的映射類ConnectionStringMapper,這個類的主要功能就是通過連接字符串建立主庫和從庫的關系,并且根據映射規(guī)則返回實際要操作的字符串,大致實現(xiàn)如下
public static class ConnectionStringMapper {//存放字符串主從關系private static readonly IDictionary<string, string[]> _mapper = new Dictionary<string, string[]>();private static readonly Random _random = new Random();static ConnectionStringMapper(){//添加數(shù)關系映射_mapper.Add(ConnectionStringConsts.MasterConnectionString, new[] { ConnectionStringConsts.SlaveConnectionString });}/// <summary>/// 獲取連接字符串/// </summary>/// <param name="masterConnectionStr">主庫連接字符串</param>/// <param name="useMaster">是否選擇讀主庫</param>/// <returns></returns>public static string GetConnectionString(string masterConnectionStr,bool useMaster){//是否走主庫if (useMaster){return masterConnectionStr;}if (!_mapper.Keys.Contains(masterConnectionStr)){throw new KeyNotFoundException("不存在的連接字符串");}//根據主庫獲取從庫連接字符串string[] slaveStrs = _mapper[masterConnectionStr];if (slaveStrs.Length == 1){return slaveStrs[0];}return slaveStrs[_random.Next(0, slaveStrs.Length - 1)];} }這個類是比較核心的操作,關于實現(xiàn)讀寫分離的核心邏輯都在這,當然你可以根據自己的具體業(yè)務實現(xiàn)類似的操作。接下來,我們將封裝一個DapperHelper的操作,雖然Dapper用起來比較簡單方便,但是依然強烈建議!!!封裝一個Dapper操作類,這樣的話可以統(tǒng)一處理數(shù)據庫相關的操作,對于以后的維護修改都非常方便,擴展性的時候也會相對容易一些
public static class DapperHelper {public static IDbConnection GetConnection(string connectionStr){return new MySqlConnection(connectionStr);}/// <summary>/// 執(zhí)行查詢相關操作/// </summary>/// <param name="sql">sql語句</param>/// <param name="param">參數(shù)</param>/// <param name="useMaster">是否去讀主庫</param>/// <returns></returns>public static IEnumerable<T> Query<T>(string sql, object param = null, bool useMaster=false){//根據實際情況選擇需要讀取數(shù)據庫的字符串string connectionStr = ConnectionStringMapper.GetConnectionString(ConnectionStringConsts.MasterConnectionString, useMaster);using (var connection = GetConnection(connectionStr)){return connection.Query<T>(sql, param);}}/// <summary>/// 執(zhí)行查詢相關操作/// </summary>/// <param name="connectionStr">連接字符串</param>/// <param name="sql">sql語句</param>/// <param name="param">參數(shù)</param>/// <param name="useMaster">是否去讀主庫</param>/// <returns></returns>public static IEnumerable<T> Query<T>(string connectionStr, string sql, object param = null, bool useMaster = false){//根據實際情況選擇需要讀取數(shù)據庫的字符串connectionStr = ConnectionStringMapper.GetConnectionString(connectionStr, useMaster);using (var connection = GetConnection(connectionStr)){return connection.Query<T>(sql, param);}}/// <summary>/// 執(zhí)行事務相關操作/// </summary>/// <param name="sql">sql語句</param>/// <param name="param">參數(shù)</param>/// <returns></returns>public static int Execute(string sql, object param = null){return Execute(ConnectionStringConsts.MasterConnectionString, sql, param);}/// <summary>/// 執(zhí)行事務相關操作/// </summary>/// <param name="connectionStr">連接字符串</param>/// <param name="sql">sql語句</param>/// <param name="param">參數(shù)</param>/// <returns></returns>public static int Execute(string connectionStr,string sql,object param=null){using (var connection = GetConnection(connectionStr)){return connection.Execute(sql,param);}}/// <summary>/// 事務封裝/// </summary>/// <param name="func">操作</param>/// <returns></returns>public static bool ExecuteTransaction(Func<IDbConnection, IDbTransaction, int> func){return ExecuteTransaction(ConnectionStringConsts.MasterConnectionString, func);}/// <summary>/// 事務封裝/// </summary>/// <param name="connectionStr">連接字符串</param>/// <param name="func">操作</param>/// <returns></returns>public static bool ExecuteTransaction(string connectionStr, Func<IDbConnection, IDbTransaction, int> func){using (var conn = GetConnection(connectionStr)){IDbTransaction trans = conn.BeginTransaction();return func(conn, trans)>0;}} }首先和大家說一句非常抱歉的話,這個類我是隨手封裝的,并沒有實驗是否可用,因為我自己的電腦并沒有安裝數(shù)據庫這套環(huán)境,但是絕對是可以體現(xiàn)我要講解的思路,希望大家多多見諒。
????在這里可以看出來Query查詢方法中我們傳遞了一個缺省參數(shù)useMaster默認值是false,主要的原因是,很多時候我們可能不能完全的使用事務性操作走主庫,讀取操作走從庫的情況,也就是我們有些場景可能要選擇性讀主庫,這時候我們可以通過這個參數(shù)去控制。當然這個字段具體的含義根據你的具體業(yè)務實際情況而定,其主要原則就是讓更多的操作能命中缺省的情況,比如你大部分讀操作都需要去主庫,那么你可以設置默認值為true,這樣的話特殊情況傳遞false,這樣的話會省下許多操作。如果你大部分讀操作都是走從庫,只有少數(shù)場景需要選擇性讀主庫,那么這個參數(shù)你可以設置為false。寫就沒有這種情況,因為無論哪種場景寫都是要在主庫進行的,除非雙主的情況,這也不是我們本次討論的重點。
使用方式
通過上述方式完成封裝之后,我們在具體數(shù)據訪問層適用的時候可以通過如下方式,如果按照默認的方式查詢可以采用如下的方式。在這里關于寫的操作我們就不展示了,因為寫的情況是固定的
string queryPersonSql = "select id,name from Person where id=@id"; var person = DapperHelper.Query<Person>(queryPersonSql, new { id = 1 }).FirstOrDefault();如果需要存在特殊情況,查詢需要選擇主庫的話可以不使用缺省參數(shù),我們可以選擇給缺省參數(shù)傳值,比如我要讓查詢走主庫
string queryPersonSql = "select id,name from Person where id=@id"; var person = DapperHelper.Query<Person>(queryPersonSql, new { id = 1 }, true).FirstOrDefault();當然,我們上面也提到了,缺省值useMaster是true還是false,這個完全可以結合自身的業(yè)務決定。如果大部分查詢都是走從庫的情況下,缺省值可以為false。如果大部分查詢情況都是走主庫的時候,缺省值可以給true。關于以上所有的相關封裝,模式并不固定,這一點可以完全結合自己的實際業(yè)務和代碼實現(xiàn),只是希望能多給大家提供一種思路,其他ORM也有自身提供了操作讀寫分離的具體實現(xiàn)。
總結
????以上就是筆者關于Dapper實現(xiàn)讀寫分離的一些個人想法,這種方法也適合其他類似Dapper偏原生SQL操作的ORM框架。這種方式還有一個優(yōu)點就是如果在現(xiàn)有的項目中,需要支持讀寫分離的時候,這種操作方式可能對原有代碼邏輯,入侵不會那么強,如果你前期封裝還比較合理的話,那么改動將會非常小。當然這只是筆者的個人的觀點,畢竟具體的實踐方式還需要結合實際項目和業(yè)務。本次我個人希望能得到大家更多關于這方面的想法,如果你有更好的實現(xiàn)方式歡迎評論區(qū)多多留言。
????歡迎掃碼關注我的公眾號????
總結
以上是生活随笔為你收集整理的关于Dapper实现读写分离的个人思考的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 初识ABP vNext(2):ABP启动
- 下一篇: Enumerable 下又有新的扩展方法