英语网站新增两个栏目,跨境电商开发,能打开国家禁止网站的浏览器,想用自己电脑做服务器做个网站概念相关为了确保多线上环境数据库的稳定性和可用性#xff0c;大部分情况下都使用了双机热备的技术。一般是一个主库一个从库或者多个从库的结构#xff0c;从库的数据来自于主库的同步。在此基础上我们可以通过数据库反向代理工具或者使用程序的方式实现读写分离#xff0… 概念相关 为了确保多线上环境数据库的稳定性和可用性大部分情况下都使用了双机热备的技术。一般是一个主库一个从库或者多个从库的结构从库的数据来自于主库的同步。在此基础上我们可以通过数据库反向代理工具或者使用程序的方式实现读写分离即主库接受事务性操作比如删除、修改、新增等操作从库接受读操作。笔者自认为读写分离解决的痛点是数据库读写负载非常高的情况下单点数据库存在读写冲突从而导致数据库压力过大出现读写操作缓慢甚至出现死锁或者拒绝服务的情况。它适用与读大于写并可以容忍一段时间内不一致的情况因为主从同步存在一定的延迟大致的实现架构图如下(图片来自于网络)。 虽然我们可以通过数据库代理实现读写分离比如mycat这类方案的优势就是对程序本身没有入侵通过代理本身来拦截sql语句分发到具体数据。甚至是更好的解决方案NewSQL去解决比如TiDB。但是还是那个原则无论使用数据库代理或者NewSQL的情况都是比较重型的解决方案会增加服务节点和运维成本有时候还没到使用这些终极解决方案的地步这时候我们会在程序中处理读写分离所以有个好的思路去在程序中解决读写分离也尤为重要。基本结构接下来我们新建三个类当然这个并不固定可以根据自己的情况新建类。首先我们新建一个ConnectionStringConsts用来存放连接字符串常量也就是用来存放读取自配置文件或者配置中心的字符串这里我直接写死当然你也可以存放多个连接字符串大致实现如下。public class ConnectionStringConsts
{/// summary/// 主库连接字符串/// /summarypublic static readonly string MasterConnectionString serverdb.master.com;Databasecrm_db;UIDroot;PWD1;/// summary/// 从库连接字符串/// /summarypublic static readonly string SlaveConnectionString serverdb.slave.com;Databasecrm_db;UIDroot;PWD1;
}
然后新建存储数据库连接字符串主从映射关系的映射类ConnectionStringMapper这个类的主要功能就是通过连接字符串建立主库和从库的关系并且根据映射规则返回实际要操作的字符串大致实现如下public static class ConnectionStringMapper
{//存放字符串主从关系private static readonly IDictionarystring, string[] _mapper new Dictionarystring, string[]();private static readonly Random _random new Random();static ConnectionStringMapper(){//添加数关系映射_mapper.Add(ConnectionStringConsts.MasterConnectionString, new[] { ConnectionStringConsts.SlaveConnectionString });}/// summary/// 获取连接字符串/// /summary/// param namemasterConnectionStr主库连接字符串/param/// param nameuseMaster是否选择读主库/param/// returns/returnspublic 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)];}
}
这个类是比较核心的操作关于实现读写分离的核心逻辑都在这当然你可以根据自己的具体业务实现类似的操作。接下来我们将封装一个DapperHelper的操作虽然Dapper用起来比较简单方便但是依然强烈建议封装一个Dapper操作类这样的话可以统一处理数据库相关的操作对于以后的维护修改都非常方便扩展性的时候也会相对容易一些public static class DapperHelper
{public static IDbConnection GetConnection(string connectionStr){return new MySqlConnection(connectionStr);}/// summary/// 执行查询相关操作/// /summary/// param namesqlsql语句/param/// param nameparam参数/param/// param nameuseMaster是否去读主库/param/// returns/returnspublic static IEnumerableT QueryT(string sql, object param null, bool useMasterfalse){//根据实际情况选择需要读取数据库的字符串string connectionStr ConnectionStringMapper.GetConnectionString(ConnectionStringConsts.MasterConnectionString, useMaster);using (var connection GetConnection(connectionStr)){return connection.QueryT(sql, param);}}/// summary/// 执行查询相关操作/// /summary/// param nameconnectionStr连接字符串/param/// param namesqlsql语句/param/// param nameparam参数/param/// param nameuseMaster是否去读主库/param/// returns/returnspublic static IEnumerableT QueryT(string connectionStr, string sql, object param null, bool useMaster false){//根据实际情况选择需要读取数据库的字符串connectionStr ConnectionStringMapper.GetConnectionString(connectionStr, useMaster);using (var connection GetConnection(connectionStr)){return connection.QueryT(sql, param);}}/// summary/// 执行事务相关操作/// /summary/// param namesqlsql语句/param/// param nameparam参数/param/// returns/returnspublic static int Execute(string sql, object param null){return Execute(ConnectionStringConsts.MasterConnectionString, sql, param);}/// summary/// 执行事务相关操作/// /summary/// param nameconnectionStr连接字符串/param/// param namesqlsql语句/param/// param nameparam参数/param/// returns/returnspublic static int Execute(string connectionStr,string sql,object paramnull){using (var connection GetConnection(connectionStr)){return connection.Execute(sql,param);}}/// summary/// 事务封装/// /summary/// param namefunc操作/param/// returns/returnspublic static bool ExecuteTransaction(FuncIDbConnection, IDbTransaction, int func){return ExecuteTransaction(ConnectionStringConsts.MasterConnectionString, func);}/// summary/// 事务封装/// /summary/// param nameconnectionStr连接字符串/param/// param namefunc操作/param/// returns/returnspublic static bool ExecuteTransaction(string connectionStr, FuncIDbConnection, IDbTransaction, int func){using (var conn GetConnection(connectionStr)){IDbTransaction trans conn.BeginTransaction();return func(conn, trans)0;}}
}
首先和大家说一句非常抱歉的话这个类我是随手封装的并没有实验是否可用因为我自己的电脑并没有安装数据库这套环境但是绝对是可以体现我要讲解的思路希望大家多多见谅。 在这里可以看出来Query查询方法中我们传递了一个缺省参数useMaster默认值是false主要的原因是很多时候我们可能不能完全的使用事务性操作走主库读取操作走从库的情况也就是我们有些场景可能要选择性读主库这时候我们可以通过这个参数去控制。当然这个字段具体的含义根据你的具体业务实际情况而定其主要原则就是让更多的操作能命中缺省的情况比如你大部分读操作都需要去主库那么你可以设置默认值为true这样的话特殊情况传递false这样的话会省下许多操作。如果你大部分读操作都是走从库只有少数场景需要选择性读主库那么这个参数你可以设置为false。写就没有这种情况因为无论哪种场景写都是要在主库进行的除非双主的情况这也不是我们本次讨论的重点。使用方式通过上述方式完成封装之后我们在具体数据访问层适用的时候可以通过如下方式如果按照默认的方式查询可以采用如下的方式。在这里关于写的操作我们就不展示了因为写的情况是固定的string queryPersonSql select id,name from Person where idid;
var person DapperHelper.QueryPerson(queryPersonSql, new { id 1 }).FirstOrDefault();
如果需要存在特殊情况查询需要选择主库的话可以不使用缺省参数我们可以选择给缺省参数传值比如我要让查询走主库string queryPersonSql select id,name from Person where idid;
var person DapperHelper.QueryPerson(queryPersonSql, new { id 1 }, true).FirstOrDefault();
当然我们上面也提到了缺省值useMaster是true还是false这个完全可以结合自身的业务决定。如果大部分查询都是走从库的情况下缺省值可以为false。如果大部分查询情况都是走主库的时候缺省值可以给true。关于以上所有的相关封装模式并不固定这一点可以完全结合自己的实际业务和代码实现只是希望能多给大家提供一种思路其他ORM也有自身提供了操作读写分离的具体实现。总结 以上就是笔者关于Dapper实现读写分离的一些个人想法这种方法也适合其他类似Dapper偏原生SQL操作的ORM框架。这种方式还有一个优点就是如果在现有的项目中需要支持读写分离的时候这种操作方式可能对原有代码逻辑入侵不会那么强如果你前期封装还比较合理的话那么改动将会非常小。当然这只是笔者的个人的观点毕竟具体的实践方式还需要结合实际项目和业务。本次我个人希望能得到大家更多关于这方面的想法如果你有更好的实现方式欢迎评论区多多留言。????欢迎扫码关注我的公众号????